1(function(){const a=document.createElement("link").relList;if(a&&a.supports&&a.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))n(o);new MutationObserver(o=>{for(const s of o)if(s.type==="childList")for(const i of s.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&n(i)}).observe(document,{childList:!0,subtree:!0});function t(o){const s={};return o.integrity&&(s.integrity=o.integrity),o.referrerPolicy&&(s.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?s.credentials="include":o.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function n(o){if(o.ep)return;o.ep=!0;const s=t(o);fetch(o.href,s)}})();function ta(){return` 2 <nav class="navbar" id="navbar"> 3 <div class="navbar__inner"> 4 <a class="navbar__logo" data-navigate="home" id="nav-logo"> 5 <img class="navbar__logo-img" src="/assets/Logo Simple 2.png" alt="BizWrap.ai" /> 6 </a> 7 8 <div class="navbar__links" id="nav-links"> 9 <a class="navbar__link" data-navigate="home" id="nav-home">Home</a> 10 <a class="navbar__link" data-navigate="contact" id="nav-contact">Contact</a> 11 <!-- AI Workshop event entry â delete this line to take the event down --> 12 <a class="btn btn--primary navbar__cta" data-navigate="workshop" id="nav-cta">AI Workshop</a> 13 </div> 14 15 <div class="navbar__mobile-toggle" id="mobile-toggle"> 16 <span></span><span></span><span></span> 17 </div> 18 </div> 19 </nav> 20 `}function aa(){const e=document.getElementById("navbar"),a=document.getElementById("mobile-toggle"),t=document.getElementById("nav-links"),n=()=>e.classList.toggle("scrolled",window.scrollY>16);window.addEventListener("scroll",n),n(),a?.addEventListener("click",()=>{a.classList.toggle("open"),t.classList.toggle("open")}),t?.querySelectorAll(".navbar__link, .navbar__cta").forEach(o=>{o.addEventListener("click",()=>{a.classList.remove("open"),t.classList.remove("open")})}),Bt()}function Bt(){const e=window.location.hash.replace("#/","")||"home";document.querySelectorAll(".navbar__link").forEach(a=>{a.classList.toggle("active",a.dataset.navigate===e)})}function sa(){return` 21 <footer class="footer" id="footer"> 22 <div class="footer__inner"> 23 <div class="footer__top"> 24 <div class="footer__brand"> 25 <div class="footer__brand-logo"> 26 <img class="footer__logo-icon" src="/assets/Logo Simple 2.png" alt="BizWrap.ai logo" style="width: auto; height: 48px;" /> 27 </div> 28 <p>We build the intelligent operating systems home-service businesses run on â the studio behind Crystal.</p> 29 </div> 30 31 <div class="footer__col"> 32 <h4 class="footer__heading">Navigate</h4> 33 <a class="footer__link" data-navigate="home">Home</a> 34 <a class="footer__link" data-navigate="home">Platform</a> 35 <a class="footer__link" data-navigate="contact">Contact</a> 36 <a class="footer__link" data-navigate="chess">Chess</a> 37 <a class="footer__link" data-navigate="admin-login">Admin sign in</a> 38 </div> 39 40 <div class="footer__col"> 41 <h4 class="footer__heading">Get in touch</h4> 42 <a class="footer__link" href="mailto:[email protected]">[email protected]</a> 43 <a class="footer__link" data-navigate="contact">Book a Workshop</a> 44 </div> 45 </div> 46 47 <div class="footer__bottom"> 48 <span class="footer__copyright">© ${new Date().getFullYear()} BizWrap.ai â Giftwrap your business.</span> 49 <div class="footer__socials"> 50 <a class="footer__social-link" href="#" aria-label="Facebook"> 51 <svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg> 52 </a> 53 <a class="footer__social-link" href="#" aria-label="LinkedIn"> 54 <svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg> 55 </a> 56 <a class="footer__social-link" href="#" aria-label="YouTube"> 57 <svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg> 58 </a> 59 <a class="footer__social-link" href="https://www.skool.com" target="_blank" rel="noopener" aria-label="Skool"> 60 <svg viewBox="0 0 24 24" fill="currentColor"><path d="M11.64 2.6a1 1 0 0 1 .72 0l9.5 3.62a1 1 0 0 1 0 1.87l-9.5 3.63a1 1 0 0 1-.72 0l-6.45-2.46v3.66a1.2 1.2 0 0 1-.35.85l-.9.9-.9-.9a1.2 1.2 0 0 1-.35-.85V8.7l-.5-.19a1 1 0 0 1 0-1.87l9.45-3.61zM6 12.86l5.28 2.02a2 2 0 0 0 1.44 0L18 12.86v2.99c0 .66-.33 1.28-.9 1.62-1.27.78-3.2 1.58-5.1 1.58s-3.83-.8-5.1-1.58a1.9 1.9 0 0 1-.9-1.62v-2.99z"/></svg> 61 </a> 62 </div> 63 </div> 64 </div> 65 </footer> 66 `}const Pt=window.matchMedia("(prefers-reduced-motion: reduce)").matches;function vt(){const e=document.querySelectorAll(".reveal:not(.revealed)");if(Pt){e.forEach(t=>t.classList.add("revealed"));return}const a=new IntersectionObserver(t=>{t.forEach(n=>
66{n.isIntersecting&&(n.target.classList.add("revealed"),a.unobserve(n.target))})},{threshold:.12,rootMargin:"0px 0px -40px 0px"});e.forEach(t=>a.observe(t))}function Fe(e,a,t="",n=1600){const o=document.getElementById(e);if(!o)return;if(Pt){o.textContent=a+t;return}const s=new IntersectionObserver(i=>{i.forEach(r=>{if(!r.isIntersecting)return;const d=performance.now();function l(c){const p=Math.min((c-d)/n,1),u=1-Math.pow(1-p,3);o.textContent=Math.floor(u*a)+t,p<1?requestAnimationFrame(l):o.textContent=a+t}requestAnimationFrame(l),s.unobserve(r.target)})},{threshold:.5});s.observe(o)}const V={os:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/></svg>',agent:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="8" width="16" height="12" rx="3"/><path d="M12 8V4"/><circle cx="12" cy="3" r="1.4"/><path d="M9 13h.01M15 13h.01"/><path d="M8 20v1M16 20v1"/></svg>',spatial:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>',gauge:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 14a3 3 0 1 0-3-3"/><path d="M12 14l4-4"/><path d="M3 12a9 9 0 1 1 18 0"/></svg>',box:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.29 7 12 12 20.71 7"/><line x1="12" y1="22" x2="12" y2="12"/></svg>',pen:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/><circle cx="11" cy="11" r="2"/></svg>',chart:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="M7 15l4-4 3 3 5-6"/></svg>',arrow:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>',chev:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 6 15 12 9 18"/></svg>'},Ke=[{label:"Inventory"},{label:"Sales & quotes"},{label:"Design studio"},{label:"Property mapping"},{label:"Crew ops"},{label:"Financials"}],At=["var(--purple)","var(--cyan)","var(--amber)"],lt=["var(--purple)","var(--cyan)","var(--amber-ink)"],G=400,q=380,Ce=196,Ie=48,wt=290,na=["#684ac5","#01c7cc","#d97706"],$e=Ke.map((e,a)=>{const t=-Math.PI/2+a*2*Math.PI/Ke.length,n=G+wt*Math.cos(t),o=q+wt*Math.sin(t),s=G-n,i=q-o,r=Ce/2,d=Ie/2;let l=1/0;Math.abs(s)>.001&&(l=Math.min(l,r/Math.abs(s))),Math.abs(i)>.001&&(l=Math.min(l,d/Math.abs(i)));const c=n+s*l,p=o+i*l,u=t+Math.PI/2,g=40,y=(c+G)/2+g*Math.cos(u),m=(p+q)/2+g*Math.sin(u);return{ox:c,oy:p,mx:y,my:m,tx:n,ty:o,angle:t}});G-86,q-86,G+86,q+86,`${$e.map((e,a)=>`<path id="fp${a}" d="M${e.ox.toFixed(1)},${e.oy.toFixed(1)} Q${e.mx.toFixed(1)},${e.my.toFixed(1)} ${G},${q}" fill="none" stroke="#dcdfe6" stroke-width="1.6"/>`).join(` 67 `)}${$e.map((e,a)=>{const t=Ke[a],n=a%3,o=e.tx-Ce/2,s=e.ty-Ie/2,i=o+14,r=e.ty-13,d=o+52,l=e.ty+5;return` 68 <g filter="url(#cardGlow${n})"> 69 <rect x="${o.toFixed(1)}" y="${s.toFixed(1)}" width="${Ce}" height="${Ie}" rx="13" fill="#ffffff" stroke="${na[n]}" stroke-width="1.5" stroke-opacity="0.6"/> 70 <rect x="${i.toFixed(1)}" y="${r.toFixed(1)}" width="26" height="26" rx="7" style="fill:${At[a%3]}"/> 71 <text x="${d.toFixed(1)}" y="${l.toFixed(1)}" font-family="Outfit, sans-serif" font-weight="600" font-size="15" fill="#1c1f24">${t.label}</text> 72 </g>`}).join("")}${$e.map((n,o)=>{const s=lt[o%3],i=o*.42;let r="";for(let d=16;d>=1;d--){const l=d/16,c=(5*Math.pow(1-l,1.4)).toFixed(1),p=(.7*Math.pow(1-l,1.2)).toFixed(2),u=(i+d*.035).toFixed(3),g=d<=4?" filter:url(#tailHaze);":"";r+=` 73 <circle r="${c}" style="fill:${s}; opacity:${p};${g}"> 74 <animateMotion dur="${2.6}s" begin="${u}
74s" repeatCount="indefinite"> 75 <mpath href="#fp${o}"/> 76 </animateMotion> 77 </circle>`}return r+=` 78 <circle r="7" style="fill:${s}; opacity:0.85; filter:url(#cometHead)"> 79 <animateMotion dur="${2.6}s" begin="${i.toFixed(2)}s" repeatCount="indefinite"> 80 <mpath href="#fp${o}"/> 81 </animateMotion> 82 </circle>`,r+=` 83 <circle r="3" style="fill:#fff"> 84 <animateMotion dur="${2.6}s" begin="${i.toFixed(2)}s" repeatCount="indefinite"> 85 <mpath href="#fp${o}"/> 86 </animateMotion> 87 </circle>`,r}).join("")}${G}${q}${G}${q}${G}${q}${G}${q}${G}${q}${G}${q}${G}${q}${G}${q}${G}${q}${G}${q}${G}`,q-6,`${G}`,q+19;const Ge=72,oa=G-Ge,ia=q-Ge,ra=` 88 <svg class="arch arch--radial" viewBox="0 0 800 760" role="img" 89 aria-label="Six business systems â inventory, sales, design, mapping, crews, finance â drafted as a blueprint, stamped into cards, then streaming into one central AI Core."> 90 <defs> 91 <pattern id="archGrid" width="34" height="34" patternUnits="userSpaceOnUse"> 92 <path d="M34 0H0V34" fill="none" stroke="#684ac5" stroke-width="1" stroke-opacity="0.12"/> 93 </pattern> 94 <linearGradient id="archHubGrad" x1="${oa}" y1="${ia}" x2="${G+Ge}" y2="${q+Ge}" gradientUnits="userSpaceOnUse"> 95 <stop offset="0" stop-color="#684ac5"/> 96 <stop offset="0.5" stop-color="#01c7cc"/> 97 <stop offset="1" stop-color="#fecb05"/> 98 </linearGradient> 99 <radialGradient id="archHubGlow" cx="50%" cy="50%" r="50%"> 100 <stop offset="0" stop-color="#684ac5" stop-opacity="0.30"/> 101 <stop offset="1" stop-color="#684ac5" stop-opacity="0"/> 102 </radialGradient> 103 <filter id="archCometHead" x="-120%" y="-120%" width="340%" height="340%"> 104 <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="b1"/> 105 <feGaussianBlur in="SourceGraphic" stdDeviation="2.5" result="b2"/> 106 <feMerge><feMergeNode in="b1"/><feMergeNode in="b2"/><feMergeNode in="SourceGraphic"/></feMerge> 107 </filter> 108 <filter id="archTailHaze" x="-60%" y="-60%" width="220%" height="220%"> 109 <feGaussianBlur in="SourceGraphic" stdDeviation="3"/> 110 </filter> 111 </defs> 112 113 <rect class="arch-grid" x="0" y="0" width="800" height="760" fill="url(#archGrid)"/> 114 115 <!-- Connectors drawn out from the center (double as comet motion paths) --> 116 ${$e.map((e,a)=>`<path id="archfp${a}" class="arch-wire" d="M${e.ox.toFixed(1)},${e.oy.toFixed(1)} Q${e.mx.toFixed(1)},${e.my.toFixed(1)} ${G},${q}" pathLength="1" style="--wd:${(.3+a*.16).toFixed(2)}s"/>`).join("")} 117 118 <!-- Converging comet flow (revealed once the system is built) --> 119 <g class="arch-flow"> 120 ${$e.map((n,o)=>{const s=lt[o%3],i=o*.42;let r="";for(let d=16;d>=1;d--){const l=d/16,c=(5*Math.pow(1-l,1.4)).toFixed(1),p=(.7*Math.pow(1-l,1.2)).toFixed(2),u=(i+d*.035).toFixed(3),g=d<=4?" filter:url(#archTailHaze);":"";r+=`<circle r="${c}" style="fill:${s}; opacity:${p};${g}"><animateMotion dur="${2.6}s" begin="${u}s" repeatCount="indefinite"><mpath href="#archfp${o}"/></animateMotion></circle>`}return r+=`<circle r="7" style="fill:${s}; opacity:0.85; filter:url(#archCometHead)"><animateMotion dur="${2.6}s" begin="${i.toFixed(2)}s" repeatCount="indefinite"><mpath href="#archfp${o}"/></animateMotion></circle>`,r+=`<circle r="3" style="fill:#fff"><animateMotion dur="${2.6}s" begin="${i.toFixed(2)}s" repeatCount="indefinite"><mpath href="#archfp${o}"/></animateMotion></circle>`,r}).join("")} 121 </g> 122 123 <!-- AI Core hub: glow + spinning flywheel, pulsing ring, gradient disc --> 124 <g class="arch-core" style="--c:var(--purple)"> 125 <g class="arch-fly"> 126 <circle cx="${G}" cy="${q}" r="118" fill="url(#archHubGlow)"/> 127 <circle cx="${G}" cy="${q}" r="82" fill="none" stroke="#e7e9ee" stroke-width="14" opacity="0.4"/> 128 <g class="arch-fly-spin"> 129 <circle cx="${G}" cy="${q}" r="82" fill="none" stroke="#684ac5" stroke-width="14" stroke-dasharray="215 300" opacity="0.9"/> 130 <circle cx="${G}" cy="${q}" r="82" fill="none" stroke="#01c7cc" stroke-width="14" stroke-dasharray="170 345" stroke-dashoffset="-215" opacity="0.9"/> 131 <circle cx="${G}" cy="${q}" r="82" fill="none" stroke="#fecb05" stroke-width="14" stroke-dasharray="130 385" stroke-dashoffset="-385" opacity="0.9"/> 132 </g> 133 </g> 134 <circle class="arch-core__pulse" cx="${G}" cy="${q}
134" r="86" fill="none" stroke="url(#archHubGrad)" stroke-width="2"/> 135 <circle class="arch-core__fill" cx="${G}" cy="${q}" r="${Ge}" fill="url(#archHubGrad)"/> 136 <text class="arch-core__label" x="${G}" y="${q-6}" text-anchor="middle" font-weight="800" font-size="23" fill="#fff">Core</text> 137 <text class="arch-core__sub" x="${G}" y="${q+18}" text-anchor="middle" font-weight="600" font-size="11.5" fill="rgba(255,255,255,0.92)">recursive flywheel</text> 138 </g> 139 140 <!-- Modules: blueprint outline â stamped into solid cards (radially staggered) --> 141 ${$e.map((e,a)=>{const t=a%3,n=e.tx-Ce/2,o=e.ty-Ie/2,s=n+14,i=e.ty-13,r=n+52,d=e.ty+5,l=.45+a*.16,c=(l+.25).toFixed(2),p=(l+.5).toFixed(2);return` 142 <g class="arch-mod" style="--c:${lt[t]}; --d:${l.toFixed(2)}s; --ld:${c}s; --md:${p}s"> 143 <rect class="arch-mod__fill" x="${n.toFixed(1)}" y="${o.toFixed(1)}" width="${Ce}" height="${Ie}" rx="13" fill="#fff"/> 144 <rect class="arch-mod__line" x="${n.toFixed(1)}" y="${o.toFixed(1)}" width="${Ce}" height="${Ie}" rx="13" pathLength="1"/> 145 <rect class="arch-mod__accent" x="${s.toFixed(1)}" y="${i.toFixed(1)}" width="26" height="26" rx="7" fill="${At[t]}"/> 146 <text class="arch-mod__label" x="${r.toFixed(1)}" y="${d.toFixed(1)}">${Ke[a].label}</text> 147 </g>`}).join("")} 148 </svg>`,je={VBW:920,VBH:545},We=[{label:"LLM · decide",cx:300,cy:135,side:"top",col:"--purple",legL:210,legR:390,d:1.5,td:0},{label:"Agent · verify",cx:470,cy:455,side:"bottom",col:"--cyan",legL:390,legR:550,d:1.8,td:1},{label:"LLM · route",cx:640,cy:135,side:"top",col:"--amber",legL:550,legR:730,d:2.1,td:2}],la=[70,210,390,550,730],da=[300,470,640],ca="M70,290 H800 C882,290 882,508 800,508 H140 C58,508 58,290 70,290 Z",pa="M800,290 C882,290 882,508 800,508 H140 C58,508 58,290 70,290",dt=e=>e.side==="top"?e.cy+31:e.cy-31,ua=e=>`M${e.cx},290 L${e.cx-48},${dt(e)} L${e.cx+48},${dt(e)} L${e.cx},290`,ha=` 149 <svg class="rl" viewBox="0 0 ${je.VBW} ${je.VBH}" role="img" 150 aria-label="A deterministic routing rail with LLM agents that decide and verify off the harness, executing back onto the rail and recursing through a flywheel, while humans handle oversight on top."> 151 <defs> 152 <pattern id="rlGrid" width="34" height="34" patternUnits="userSpaceOnUse"> 153 <path d="M34 0H0V34" fill="none" stroke="#684ac5" stroke-width="1" stroke-opacity="0.12"/> 154 </pattern> 155 <linearGradient id="rlRailGrad" x1="70" y1="0" x2="760" y2="0" gradientUnits="userSpaceOnUse"> 156 <stop offset="0" stop-color="#684ac5"/><stop offset="0.5" stop-color="#01c7cc"/><stop offset="1" stop-color="#fecb05"/> 157 </linearGradient> 158 <linearGradient id="rlDiscGrad" x1="777" y1="257" x2="843" y2="323" gradientUnits="userSpaceOnUse"> 159 <stop offset="0" stop-color="#684ac5"/><stop offset="0.55" stop-color="#01c7cc"/><stop offset="1" stop-color="#fecb05"/> 160 </linearGradient> 161 <marker id="rlArrow" markerWidth="9" markerHeight="9" refX="4" refY="4" orient="auto"> 162 <path d="M1,1 L7,4 L1,7" fill="none" stroke="#f2705a" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/> 163 </marker> 164 <!-- Soft glow halo behind the flywheel hub --> 165 <radialGradient id="rlHubGlow" cx="50%" cy="50%" r="50%"> 166 <stop offset="0" stop-color="#01c7cc" stop-opacity="0.30"/> 167 <stop offset="1" stop-color="#01c7cc" stop-opacity="0"/> 168 </radialGradient> 169 <!-- Comet bloom for traveling nodes (borrowed from the convergence funnel) --> 170 <filter id="rlGlowHead" x="-160%" y="-160%" width="420%" height="420%"> 171 <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="b1"/> 172 <feGaussianBlur in="SourceGraphic" stdDeviation="2.5" result="b2"/> 173 <feMerge><feMergeNode in="b1"/><feMergeNode in="b2"/><feMergeNode in="SourceGraphic"/></feMerge> 174 </filter> 175 <filter id="rlGlow" x="-150%" y="-150%" width="400%" height="400%"> 176 <feGaussianBlur in="SourceGraphic" stdDeviation="3" result="b"/> 177 <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge> 178 </filter> 179 <filter id="rlHaze" x="-80%" y="-80%" width="260%" height="260%"> 180 <feGaussianBlur in="SourceGraphic" stdDeviation="2.4"/> 181 </filter> 182 </defs> 183 184 <rect class="rl-grid" x="0" y="0" width="${je.VBW}" height="${je.VBH}" fill="url(#rlGrid)"/> 185 186 <!-- Human oversight lane --> 187 <g class="rl-lane-g"> 188 <rect class="rl-lane" x="70" y="32" width="780" height="54" rx="16"/> 189 <g class="rl-human" transform="translate(90,47) scale(0.92)" fill="none" stroke="#0a0a0b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 190 <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/> 191 <circle cx="12" cy="7" r="4"/> 192 </g> 193 <text class="rl-lane__title" x="118" y="64" font-weight="700" font-size="15" fill="#0a0a0b">Human oversight</text> 194 <text x="838" y="64" text-anchor="end" font-weight="500" font-size="13.5" fill="#5b6472">Dashboards · Exceptions · Monitoring · Problem-solving</text> 195 </g> 196 <!-- Exceptions escalate to the humans --> 197 <path class="rl-esc" d="M640,104 L640,90" marker-end="url(#rlArrow)"/> 198 199 <!-- Deterministic rail (drawn first â "laying the track") --> 200 <path class="rl-rail" d="M70,290 H760" pathLength="1"/> 201 <!-- Recursive feedback / flywheel return --> 202 <path class="rl-return" d="${pa}"/> 203 <text class="rl-rlabel" x="440" y="500" text-anchor="middle" font-weight="500" font-size="12.5" fill="#5b6472">state feeds back â the loop compounds</text> 204 205 <!-- Invisible motion paths --> 206 <path id="rlLoop" d="${ca}" fill="none" stroke="none"/> 207 ${We.map((e,a)=>`<path id="rlBranch${a}" d="${ua(e)}" fill="none" stroke="none"/>`).join("")} 208 209 <!-- Dashed legs converge on the red verify gate (the model taps the rail there) --> 210 ${We.map(e=>{const a=dt(e);return` 211 <path class="rl-leg" d="M${e.cx-48},${a}
211 L${e.cx},290" style="--d:${(e.d+.05).toFixed(2)}s"/> 212 <path class="rl-leg" d="M${e.cx+48},${a} L${e.cx},290" style="--d:${(e.d+.12).toFixed(2)}s"/>`}).join("")} 213 214 <!-- Routing waypoints + verify gates --> 215 ${la.map((e,a)=>`<circle class="rl-ring" cx="${e}" cy="290" r="8" style="--d:${(.9+a*.1).toFixed(2)}s"/>`).join("")} 216 ${da.map((e,a)=>`<circle class="rl-gate" cx="${e}" cy="290" r="7" style="--d:${(1.25+a*.12).toFixed(2)}s"/>`).join("")} 217 218 <!-- Recursive flywheel --> 219 <g class="rl-fly-g"> 220 <circle cx="810" cy="290" r="54" fill="url(#rlHubGlow)"/> 221 <circle cx="810" cy="290" r="33" fill="none" stroke="#e7e9ee" stroke-width="8"/> 222 <g class="rl-fly-spin"> 223 <circle cx="810" cy="290" r="33" fill="none" stroke="#684ac5" stroke-width="8" stroke-dasharray="55 152"/> 224 <circle cx="810" cy="290" r="33" fill="none" stroke="#01c7cc" stroke-width="8" stroke-dasharray="45 162" stroke-dashoffset="-70"/> 225 <circle cx="810" cy="290" r="33" fill="none" stroke="#fecb05" stroke-width="8" stroke-dasharray="38 169" stroke-dashoffset="-130"/> 226 </g> 227 <circle cx="810" cy="290" r="22" fill="url(#rlDiscGrad)"/> 228 <text x="810" y="352" text-anchor="middle" font-weight="600" font-size="12" fill="#5b6472">recursive flywheel</text> 229 </g> 230 231 <!-- LLM / agent boxes --> 232 ${We.map(e=>` 233 <g class="rl-box" style="--d:${e.d}s"> 234 <rect class="rl-box__bg" x="${e.cx-77}" y="${e.cy-31}" width="154" height="62" rx="14"/> 235 <rect class="rl-box__dot" x="${e.cx-60}" y="${e.cy-9}" width="18" height="18" rx="5" fill="var(${e.col})"/> 236 <text class="rl-box__label" x="${e.cx-34}" y="${e.cy+5}">${e.label}</text> 237 </g>`).join("")} 238 239 <!-- Decision tokens: glowing nodes that ride the dashed legs up from each red gate --> 240 ${We.map((e,a)=>` 241 <g class="rl-decide"> 242 <circle r="5" style="fill:var(${e.col}); opacity:0.85; filter:url(#rlGlow)"> 243 <animateMotion dur="3.2s" begin="${e.td}s" repeatCount="indefinite" calcMode="linear"><mpath href="#rlBranch${a}"/></animateMotion> 244 </circle> 245 <circle r="2.2" fill="#fff"> 246 <animateMotion dur="3.2s" begin="${e.td}s" repeatCount="indefinite" calcMode="linear"><mpath href="#rlBranch${a}"/></animateMotion> 247 </circle> 248 </g>`).join("")} 249 250 <!-- Main token: a glowing comet streaking along the deterministic rail + flywheel loop --> 251 <g class="rl-token"> 252 ${(()=>{const n="#01c7cc";let o="";for(let s=12;s>=1;s--){const i=s/12,r=(5.4*Math.pow(1-i,1.3)).toFixed(1),d=(.7*Math.pow(1-i,1.1)).toFixed(2),l=(s*.02).toFixed(3),c=s<=4?" filter:url(#rlHaze);":"";o+=` 253 <circle r="${r}" style="fill:${n}; opacity:${d};${c}"> 254 <animateMotion dur="${7.5}s" begin="${l}s" repeatCount="indefinite" calcMode="linear"><mpath href="#rlLoop"/></animateMotion> 255 </circle>`}return o+=` 256 <circle r="7" style="fill:${n}; opacity:0.9; filter:url(#rlGlowHead)"> 257 <animateMotion dur="${7.5}s" begin="0s" repeatCount="indefinite" calcMode="linear"><mpath href="#rlLoop"/></animateMotion> 258 </circle> 259 <circle r="3" style="fill:#fff"> 260 <animateMotion dur="${7.5}s" begin="0s" repeatCount="indefinite" calcMode="linear"><mpath href="#rlLoop"/></animateMotion> 261 </circle>`,o})()} 262 </g> 263 264 <!-- Deterministic routing logic label --> 265 <g class="rl-pill-g"> 266 <rect class="rl-pill" x="20" y="232" width="232" height="36" rx="18"/> 267 <text x="136" y="255" text-anchor="middle" font-weight="700" font-size="13.5" fill="#0a0a0b">Deterministic routing logic</text> 268 </g> 269 </svg>`,Gt=[{id:"ltv",label:"LTV",sub:"Lifetime value",value:4820,step:[6,55]},{id:"aov",label:"AOV",sub:"Avg order value",value:1240,step:[3,24]},{id:"cac",label:"CAC",sub:"Acquisition cost",value:312,step:[1,8],goodDown:!0,floor:150},{id:"mrr",label:"MRR",sub:"Monthly revenue",value:86400,step:[60,520]},{id:"cvr",label:"Conv.",sub:"Lead â close",value:34.2,step:[.1,.5],pct:!0,dec:1,cap:64},{id:"ret",label:"Retention",sub:"Net 12-mo",value:96.1,step:[.05,.25],pct:!0,dec:1,cap:99.4}];function ct(e,a=e.value){return e.pct?a.toFixed(e.dec)+"%":"$"+Math.round(a).toLocaleStr
269ing("en-US")}function Et(){return` 270 <!-- HERO --> 271 <section class="hero hero--home" id="home-hero"> 272 <div class="hero__grid hero__grid--center"> 273 <div class="hero__copy"> 274 <div class="brandmark reveal"> 275 <img class="brandmark__photo" src="/assets/BizWrap.png" alt="BizWrap.ai â Giftwrap your business." style="display:none" 276 onload="this.style.display='block'; this.nextElementSibling.style.display='none';" /> 277 <div class="brandmark__lockup"> 278 <img class="brandmark__icon" src="/assets/logo-mark.svg" alt="" /> 279 <div class="brandmark__words"> 280 <div class="brandmark__word"><span class="b">Biz</span><span class="w">Wrap</span><span class="ai">.ai</span></div> 281 <div class="brandmark__tag">Giftwrap your business.</div> 282 </div> 283 </div> 284 </div> 285 <h1 class="hero__title reveal reveal-delay-1"> 286 The operating system your <span class="grad-text">business thrives</span> on. 287 </h1> 288 <p class="hero__subtitle reveal reveal-delay-2"> 289 BizWrap builds intelligent operating systems and AI agents that wrap every part of your operation â inventory, mapping, design, sales, crews, finance â into one system with a single AI core. We're the studio behind <strong>Crystal</strong>. 290 </p> 291 <div class="hero__actions reveal reveal-delay-3"> 292 <a class="btn btn--primary btn--lg btn--icon-right" href="#home-capabilities" id="hero-cta-platform"> 293 Explore the Platform ${V.arrow} 294 </a> 295 <a class="btn btn--ghost btn--lg" data-navigate="contact" id="hero-cta-contact">Book a Workshop</a> 296 </div> 297 </div> 298 </div> 299 </section> 300 301 <!-- REAL-TIME SENSING --> 302 <section class="section" id="home-live" style="padding-top: 10rem;"> 303 <div class="container"> 304 <div class="section__head reveal"> 305 <span class="eyebrow eyebrow--center">Real-time sensing</span> 306 <h2>Your business, <span class="grad-text">sensed live</span></h2> 307 <p>The core doesn't just store numbers â it watches them. Every metric updates the moment reality does.</p> 308 </div> 309 <div class="live-grid reveal"> 310 ${Gt.map(e=>` 311 <div class="lm"> 312 <div class="lm__top"> 313 <span class="lm__label">${e.label}</span> 314 <span class="lm__live"><i></i> live</span> 315 </div> 316 <div class="lm__value" id="lm-${e.id}">${ct(e)}</div> 317 <div class="lm__foot"> 318 <span class="lm__sub">${e.sub}</span> 319 <span class="lm__delta is-up" id="lm-${e.id}-d">â² 0.0%</span> 320 </div> 321 </div>`).join("")} 322 </div> 323 324 <!-- ARCHITECT GRAPHIC â blueprint â built (below KPI cards) --> 325 <p class="arch-caption reveal" style="margin-top: var(--space-12);">First, we architect & build it â from blueprint to built.</p> 326 <div class="arch-stage reveal"> 327 ${ra} 328 </div> 329 330 <!-- CORE RECURSIVE FLYWHEEL â not just consulting --> 331 <div class="section__head reveal" style="margin-top: 10rem;"> 332 <span class="eyebrow eyebrow--center">Not just consulting</span> 333 <h2>Deterministic routing, <span class="grad-text">recursive self-healing flywheel</span></h2> 334 <p>Strategy is where most firms stop. We keep going â architecting and building the AI-native applications and operating systems your business runs on, then laying the deterministic routing logic that makes them run themselves. LLM agents decide, verify their output off the harness, and execute â and it compounds into a recursive, self-healing flywheel. Humans evolve workflows: dashboards, exception handling, monitoring, and problem-solving.</p> 335 </div> 336 337 <p class="arch-caption arch-caption--gap reveal">Then it runs itself â deterministic routing into a recursive, self-healing flywheel.</p> 338 <div class="arch-stage reveal"> 339 ${ha} 340 </div> 341 </div> 342 </section> 343 344 <!-- PROCESS â how we engage --> 345 <section class="section" id="home-process" style="padding-top: 5rem;"> 346 <div class="container"> 347 <div class="section__head reveal">
348 <span class="eyebrow eyebrow--center">How we engage</span> 349 <h2>From <span class="grad-text">friction</span> to flow</h2> 350 <p>We don't hand you a tool and walk away. We map your business, architect the system, and stay to make it smarter.</p> 351 </div> 352 <div class="flow"> 353 <div class="flow__step reveal reveal-delay-1"> 354 <div class="flow__num">1</div> 355 <h4>Map</h4> 356 <p>We learn your operation end to end and chart every discipline, handoff, and source of truth.</p> 357 </div> 358 <div class="flow__step reveal reveal-delay-2"> 359 <div class="flow__num">2</div> 360 <h4>Architect</h4> 361 <p>We design the unified system and its AI core, prototyping and iterating against real workflows.</p> 362 </div> 363 <div class="flow__step reveal reveal-delay-3"> 364 <div class="flow__num">3</div> 365 <h4>Deploy & evolve</h4> 366 <p>We launch on every device, secured by role â then keep tuning the intelligence season after season.</p> 367 </div> 368 </div> 369 </div> 370 </section> 371 372 <!-- AGENT LOOP â how the intelligence works --> 373 <section class="section" id="home-loop" style="padding-top: 5rem;"> 374 <div class="container"> 375 <div class="section__head reveal"> 376 <span class="eyebrow eyebrow--center">How the intelligence works</span> 377 <h2>Agents that <span class="grad-text">close the loop</span></h2> 378 <p>Our systems don't wait to be asked. They perceive the state of your business, reason over it, take action, and get sharper every cycle.</p> 379 </div> 380 <div class="loop"> 381 <div class="loop__step reveal reveal-delay-1" data-step="01"> 382 <h4>Perceive</h4> 383 <p>Live data from inventory, jobs, crews, and finance streams into one model of the business.</p> 384 <span class="loop__arrow">${V.chev}</span> 385 </div> 386 <div class="loop__step reveal reveal-delay-2" data-step="02"> 387 <h4>Reason</h4> 388 <p>The AI core cross-references every domain to surface risks, bottlenecks, and the next best move.</p> 389 <span class="loop__arrow">${V.chev}</span> 390 </div> 391 <div class="loop__step reveal reveal-delay-3" data-step="03"> 392 <h4>Act</h4> 393 <p>It drafts quotes, sequences routes, builds pick lists, and triggers workflows â with you in command.</p> 394 <span class="loop__arrow">${V.chev}</span> 395 </div> 396 <div class="loop__step reveal reveal-delay-4" data-step="04"> 397 <h4>Learn</h4> 398 <p>Every season, install, and decision feeds back in. The system you run on keeps getting smarter.</p> 399 </div> 400 </div> 401 </div> 402 </section> 403 404 <!-- WHAT WE BUILD --> 405 <section class="section" id="home-capabilities" style="padding-top: 5rem;"> 406 <div class="container"> 407 <div class="section__head reveal"> 408 <span class="eyebrow eyebrow--center">What we build</span> 409 <h2 class="build-title"> 410 <span class="bt-line"><span style="color: var(--purple)">Ecosystem</span> of <span style="color: var(--cyan)">applications</span></span> 411 <span class="bt-arrow" aria-hidden="true"> 412 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="4" x2="12" y2="17"/><polyline points="6 11 12 17 18 11"/></svg> 413 </span> 414 <span class="bt-line" style="color: var(--amber)">operating system</span> 415 </h2> 416 <p>Most software records what happened. We build intelligence that tells you what to do next â and then does it.</p> 417 </div> 418 <div class="cap-grid"> 419 <div class="cap-card reveal reveal-delay-1"> 420 <div class="cap-card__icon">${V.os}</div> 421 <h3 class="cap-card__title">Business Operating Systems</h3> 422 <p class="cap-card__desc">
422One living interface that collapses inventory, logistics, sales, design, and finance into a single source of truth. Every module speaks the same language and draws from the same data.</p> 423 </div> 424 <div class="cap-card reveal reveal-delay-2"> 425 <div class="cap-card__icon">${V.agent}</div> 426 <h3 class="cap-card__title">AI Agents & Copilots</h3> 427 <p class="cap-card__desc">A conversational core woven into every screen. Ask in plain language or by voice â it reasons across the whole dataset, orchestrates the answer, and acts on it.</p> 428 </div> 429 <div class="cap-card reveal reveal-delay-3"> 430 <div class="cap-card__icon">${V.spatial}</div> 431 <h3 class="cap-card__title">Field & Spatial Systems</h3> 432 <p class="cap-card__desc">GPS-anchored property mapping, CAD-grade design boards, QR-driven pick lists, and crew orchestration â the machinery that turns a signed contract into a finished install.</p> 433 </div> 434 </div> 435 </div> 436 </section> 437 438 439 440 <!-- CRYSTAL CASE STUDY --> 441 <section class="section section--alt" id="home-crystal"> 442 <div class="container"> 443 <div class="section__head reveal"> 444 <span class="eyebrow eyebrow--center">Proof of concept</span> 445 <h2><span class="grad-text">Flagship</span> build</h2> 446 <p>We didn't just imagine this. We built an entire operating system for a real, brutally complex business â and it runs the whole show.</p> 447 </div> 448 449 <div class="case reveal"> 450 <div class="case__story"> 451 <div class="case__story-copy"> 452 <h3 class="case__title grad-text">Crystal</h3> 453 <p class="case__lede">A full operating system for Murphy Christmas Lighting â tens of thousands of assets, fleets of crews, hundreds of mapped estates, and an entire year of revenue compressed into a few decisive weeks. Where competitors juggle a dozen disconnected tools, Crystal is one living organism.</p> 454 <a class="btn btn--primary btn--icon-right" data-navigate="contact" id="crystal-cta">Book a demo ${V.arrow}</a> 455 </div> 456 457 <div class="case__visual"> 458 <div class="appwin" aria-hidden="true"> 459 <div class="appwin__bar"><i></i><i></i><i></i><span class="appwin__url">crystal · live</span></div> 460 <div class="appwin__body"> 461 <aside class="appwin__side"> 462 <span class="appwin__logo"></span> 463 <span class="appwin__navi"></span><span class="appwin__navi"></span><span class="appwin__navi"></span><span class="appwin__navi"></span><span class="appwin__navi"></span> 464 </aside> 465 <div class="appwin__main"> 466 <div class="appwin__kpis"> 467 <span class="appwin__kpi"></span><span class="appwin__kpi"></span><span class="appwin__kpi"></span> 468 </div> 469 <div class="appwin__chart"> 470 <span style="height:42%"></span><span style="height:68%"></span><span style="height:54%"></span><span style="height:88%"></span><span style="height:60%"></span><span style="height:96%"></span><span style="height:74%"></span><span style="height:50%"></span> 471 </div> 472 <div class="appwin__tiles"> 473 <span class="appwin__tile"></span><span class="appwin__tile"></span> 474 </div> 475 </div> 476 </div> 477 </div> 478 </div> 479 </div> 480 481 <div class="domain-grid"> 482 <div class="domain"> 483 <div class="domain__top"> 484 <div class="domain__icon">${V.gauge}</div> 485 <div> 486 <div class="domain__name">I-Sight</div> 487 <div class="domain__role">The command center</div> 488 </div> 489 </div> 490 <p class="domain__desc">Live dashboards, scheduling, operational playbooks, and company knowledge â a real-time read of the entire enterprise the moment you log in.</p> 491 <div class="domain__tags"><span class="chip">Dashboards</span><span class="chip">Scheduling</span><span class="chip">Playbooks</span></div> 492 </div> 493 494 <div class="domain"> 495 <div class="domain__top"> 496 <div class="domain__icon">${V.box}</div> 497 <div> 498 <div class="domain__name">Giftwrap</div> 499 <div class="domain__role">The logistics engine</div> 500 </div> 501 </div> 502 <p class="domain__desc">Spatial warehouse mapping, QR pick lists, kit assembly, audits, low-stock intelligence, a sales pipeline, vendor management, and live asset valuation.</p> 503 <div class="domain__tags"><span class="chip">Warehouse</span><span class="chip">QR pick lists</span><span class="chip">Pipeline</span></div> 504 </div> 505 506 <div class="domain"> 507 <div class="domain__top"> 508 <div class="domain__icon">${V.pen}</div> 509 <div> 510 <div class="domain__name">Studio</div> 511 <div class="domain__role">The creative forge</div> 512 </div> 513 </div> 514 <p class="domain__desc">A CAD-grade Design Board that renders lighting concepts onto a property, GPS-anchored project mapping of every job site, and a built-in marketing studio.</p> 515 <div class="domain__tags"><span class="chip">Design Board</span><span class="chip">Project mapping</span><span class="chip">Marketing</span></div> 516 </div> 517 518 <div class="domain"> 519 <div class="domain__top"> 520 <div class="domain__icon">${V.chart}</div> 521 <div> 522 <div class="domain__name">Executive</div> 523 <div class="domain__role">The boardroom</div> 524 </div> 525 </div> 526 <p class="domain__desc">Financials, reporting, and intake automation that translate ground-level activity into the numbers that steer the company.</p> 527 <div class="domain__tags"><span class="chip">Financials</span><span class="chip">Reporting</span><span class="chip">Intake</span></div> 528 </div> 529 </div> 530 531 <div class="intel"> 532 <div class="intel__orb"></div> 533 <div> 534 <h4>Crystal Intelligence</h4> 535 <p>At the center sits Crystal herself â a conversational AI core woven into every screen. Ask a question in plain language or by voice and she reasons across inventory, projects, vendors, and finances. This is the difference between software that records what happened and intelligence that tells you what to do next.</p> 536 </div> 537 </div> 538 </div> 539 </div> 540 </section> 541 542 <!-- MULTIMODALITY --> 543 <section class="section" id="home-multimodality" style="padding-bottom: 0;"> 544 <div class="container"> 545 <div class="section__head reveal">
546 <span class="eyebrow eyebrow--center">Capabilities</span> 547 <h2 class="grad-text">Multimodality</h2> 548 <p>Each capability is powerful alone. Together â drawing from the same data and the same intelligence â they become an operating system.</p> 549 </div> 550 </div> 551 </section> 552 553 <!-- METRICS --> 554 <section class="section" id="home-metrics" style="padding-top: var(--space-8);"> 555 <div class="container"> 556 <div class="metrics reveal"> 557 <div class="metric"><div class="metric__num" id="m-assets">0</div><div class="metric__label">Assets orchestrated</div></div> 558 <div class="metric"><div class="metric__num" id="m-props">0</div><div class="metric__label">Properties mapped</div></div> 559 <div class="metric"><div class="metric__num" id="m-domains">0</div><div class="metric__label">Operating domains</div></div> 560 <div class="metric"><div class="metric__num" id="m-truth">0</div><div class="metric__label">Source of truth</div></div> 561 </div> 562 </div> 563 </section> 564 565 566 <!-- MEET THE FOUNDER --> 567 <section class="section" id="home-founder" style="padding-top: 5rem;"> 568 <div class="container"> 569 <div class="section__head reveal"> 570 <span class="eyebrow eyebrow--center">About</span> 571 <h2>Meet the <span class="grad-text">Founder</span></h2> 572 </div> 573 <div class="founder reveal"> 574 <div class="founder__photo-wrap"> 575 <img class="founder__photo" src="/assets/founder.jpg" alt="Joey Murphy â Founder of BizWrap.ai" /> 576 </div> 577 <div class="founder__bio"> 578 <h3 class="founder__name">Joey Murphy</h3> 579 <p>Joey Murphy is a business operator, technologist, and founder of BizWrap.ai. He built and scaled Murphy Christmas Lighting into one of the most operationally s
579ophisticated home-service companies in the industry â managing tens of thousands of assets, hundreds of mapped properties, and full seasonal logistics.</p> 580 <p>When off-the-shelf software couldn't keep up, he started building his own. That side project became Crystal â a full operating system with an AI core that now runs every corner of the business, from warehouse to rooftop to boardroom.</p> 581 <p>Today, through BizWrap.ai, Joey is turning that hard-won playbook into intelligent systems for other operators who refuse to outgrow their tools.</p> 582 <a class="btn btn--primary btn--icon-right" data-navigate="contact" id="founder-cta"> 583 Start a conversation ${V.arrow} 584 </a> 585 </div> 586 </div> 587 </div> 588 </section> 589 590 <!-- COMPANY INTRO VIDEO --> 591 <section class="section" id="home-video"> 592 <div class="container"> 593 <div class="intro-video reveal"> 594 <video class="intro-video__player" id="intro-video" 595 playsinline preload="auto"> 596 <source src="/assets/Company_intro_video_technology_202605252356.mp4" type="video/mp4" /> 597 </video> 598 <button class="intro-video__mute" id="intro-video-mute" aria-label="Toggle audio"> 599 <svg class="ico-sound" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg> 600 <svg class="ico-muted" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><line x1="23" y1="9" x2="17" y2="15"/><line x1="17" y1="9" x2="23" y2="15"/></svg> 601 </button> 602 </div> 603 </div> 604 </section> 605 606 <!-- CTA --> 607 <section class="section" id="home-cta"> 608 <div class="container"> 609 <div class="cta reveal"> 610 <span class="eyebrow eyebrow--center">Let's build it</span> 611 <h2>Turn your company into a<br>technology company.</h2> 612 <p>Tell us how your business runs today. We'll show you the system it could run on tomorrow.</p> 613 <div class="hero__actions"> 614 <a class="btn btn--primary btn--lg btn--icon-right" data-navigate="contact" id="home-cta-button"> 615 Start a conversation ${V.arrow} 616 </a> 617 </div> 618 </div> 619 </div> 620 </section> 621 `}function _t(){ma(),vt(),Fe("m-assets",40,"k+"),Fe("m-props",300,"+"),Fe("m-domains",4,""),Fe("m-truth",1,""),va();const e=n=>{const o=document.querySelector(n);if(!o)return;if(window.matchMedia("(prefers-reduced-motion: reduce)").matches){o.classList.add("is-built");return}new IntersectionObserver(([i],r)=>{i.isIntersecting&&(o.classList.add("is-building"),r.disconnect())},{threshold:.25}).observe(o)};e(".arch"),e(".rl");const a=document.getElementById("intro-video");if(a){const n=new IntersectionObserver(([o])=>{o.isIntersecting&&(a.play().catch(()=>{a.muted=!0,a.play()}),n.disconnect())},{threshold:.4});n.observe(a)}const t=document.getElementById("intro-video-mute");if(a&&t){const n=()=>t.classList.toggle("is-muted",a.muted);n(),t.addEventListener("click",()=>{a.muted=!a.muted,n()})}a&&a.addEventListener("click",()=>{(a.paused||a.ended)&&(a.currentTime=0,a.play())}),document.querySelectorAll('a[href^="#home-"]').forEach(n=>{n.addEventListener("click",o=>{o.preventDefault();const s=document.querySelector(n.getAttribute("href"));s&&s.scrollIntoView({behavior:"smooth",block:"start"})})})}function ma(){const e=document.getElementById("page-content");e&&e.querySelectorAll(".section").forEach((a,t)=>{const n=t%2===0?"reveal--from-left":"reveal--from-right";a.querySelectorAll(".reveal").forEach(o=>{o.classList.contains("section__head")?o.classList.add("jm-head"):o.classList.add(n)})})}function va(){window.matchMedia("(prefers-reduced-motion: reduce)").matches||Gt.forEach(e=>{const a=document.getElementById("lm-"+e.id),t=document.getElementById("lm-"+e.id+"-d");if(!a)return;const n=()=>{document.body.contains(a)&&(fa(e,a,t),setTimeout(n,1300+Math.random()*2400))};setTimeout(n,500+Math.random()*1800)})}function fa(e,a,t){const[n,o]=e.step,s=n+Math.random()*(o-n),i=Math.random()<(e.goodDown?.25:.8);let r=e.value+(i?s:-s);e.pct&&(r=Math.min(e.cap,Math.max(0,r))),e.floor&&(r=Math.max(e.floor,r));const d=e.value,l=d?Math.abs((r-d)/d*100):0;if(ga(a,d,r,e),e.value=r,t){const c=e.goodDown?r<d:r>d;t.textContent=(r>=d?"â² ":"â¼ ")+l.toFixed(1)+"%",t.className="lm__delta "+(c?"is-up":"is-down")}a.classList.remove("flash"),a.offsetWidth,a.classList.add("flash")}function ga(e,a,t,n,o=650){const s=performance.now();function i(r){const d=Math.min((r-s)/o,1),l=1-Math.pow(1-d,3);e.textContent=ct(n,a+(t-a)*l),d<1?requestAnimationFrame(i):e.textContent=ct(n,t)}requestAnimationFrame(i)}const ya=!["localhost","127.0.0.1"].includes(window.location.hostname),O=ya?"/api":"http://localhost:5000/api",ba=[{question:"What exactly does BizWrap build?",answer:"Intelligent operating systems and AI agents for home-service businesses. Instead of stitching together a dozen tools, we build one unified system â with a conversational AI core â that runs your inventory, mapping, design, sales, crews, and finance."},{question:"Do I have to be a Christmas-lighting company?",answer:"Not at all. Crystal happens to power a large-scale holiday-lighting operation, but the same architecture fits any field-heavy, seasonal, or multi-crew home-service business â landscaping, pools, restoration, HVAC, and more."},{question:"How long does it take to build a system?",answer:"We start with a focused mapping phase, then ship a working core in weeks and grow it module by module. You see value early instead of waiting for one giant launch."},{question:"Do I need a technical team to run it?",answer:"No. The whole point of an AI core is that you talk to your business in plain language. We handle the engineering, the deployment, and the ongoing tuning."},{question:"What does it cost?",answer:"Every system is scoped to the operation it runs. We offer fixed-scope builds and ongoing partnerships. Reach out and we will put real numbers to your real workflows."}];function wa(){return` 622 <!-- HERO --> 623 <section class="hero hero--page hero--compact" id="contact-hero"> 624 <div class="hero__grid hero__grid--center"> 625 <div class="hero__copy"> 626 <h1 class="hero__title reveal">Book a <span class="grad-text">demo</span>.</h1> 627 <p class="hero__subtitle reveal reveal-delay-1"> 628 Tell us how your business runs today and we'll show you the system it could run on. We reply within 24 hours. 629 </p> 630 </div> 631 </div> 632 </section> 633 634 <!-- CONTACT CONTENT --> 635 <section class="section" id="contact-content" style="padding-top:var(--space-4); padding-bottom:var(--space-16);"> 636 <div class="container"> 637 <div class="section__head reveal" style="margin-bottom:var(--space-10);">
638 <span class="eyebrow eyebrow--center">Get in touch</span> 639 <h2 style="margin-top:var(--space-4);">Let's <span class="grad-text">talk</span></h2> 640 </div> 641 <div class="contact-layout"> 642 643 <!-- FORM --> 644 <div class="form-card reveal" id="form-card-container"> 645 <h3 style="margin-bottom: var(--space-6);">Send us a message</h3> 646 <form id="contact-form"> 647 <div class="form-group"> 648 <label class="form-label" for="contact-name">Full name</label> 649 <input class="form-input" type="text" id="contact-name" name="name" placeholder="Joey Murphy" required> 650 </div> 651 652 <div class="form-group"> 653 <label class="form-label" for="contact-email">Email address</label> 654 <input class="form-input" type="email" id="contact-email" name="email" placeholder="[email protected]" required> 655 </div> 656 657 <div class="form-group"> 658 <label class="form-label" for="contact-company">Company</label> 659 <input class="form-input" type="text" id="contact-company" name="company" placeholder="Your business"> 660 </div> 661 662 <div class="form-group"> 663 <label class="form-label" for="contact-service">What are you looking to build?</label> 664 <select class="form-select" id="contact-service" name="service"> 665 <option value="" disabled selected>Select an option...</option> 666 <option value="os">Full operating system</option> 667 <option value="agent">AI agent / copilot</option> 668 <option value="spatial">Property mapping & field tools</option> 669 <option value="design">Design studio</option> 670 <option value="logistics">Warehouse & logistics</option> 671 <option value="financials">Financials & reporting</option> 672 <option value="other">Other / not sure yet</option> 673 </select> 674 </div> 675 676 <div class="form-group"> 677 <label class="form-label" for="contact-message">Tell us about your operation</label> 678 <textarea class="form-textarea" id="contact-message" name="message" placeholder="How does your business run today? Where does it hurt?" required></textarea> 679 </div> 680 681 <button class="btn btn--primary btn--lg" type="submit" id="contact-submit" style="width: 100%;"> 682 <span id="submit-text">Send message</span> 683 <span id="submit-loading" style="display:none;">Sending...</span> 684 </button> 685 686 <div id="form-status" class="form-status"></div> 687 </form> 688 </div> 689 690 <!-- INFO + FAQ --> 691 <div class="reveal reveal-delay-2"> 692 <h3 style="margin-bottom: var(--space-6);">Contact</h3> 693 694 <div class="contact-info-card"> 695 <div class="contact-info-card__icon"> 696 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg> 697 </div> 698 <div> 699 <div class="contact-info-card__label">Email</div> 700 <a class="contact-info-card__value" href="mailto:[email protected]">[email protected]</a> 701 </div> 702 </div> 703 704 <div class="contact-info-card"> 705 <div class="contact-info-card__icon"> 706 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg> 707 </div> 708 <div> 709 <div class="contact-info-card__label">Based in</div> 710 <div class="contact-info-card__value">United States</div> 711 </div> 712 </div> 713 714 <div class="contact-info-card"> 715 <div class="contact-info-card__icon"> 716 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> 717 </div> 718 <div> 719 <div class="contact-info-card__label">Response time</div> 720 <div class="contact-info-card__value">Within 24 hours</div> 721 </div> 722 </div> 723 724 <h3 style="margin-top: var(--space-10); margin-bottom: var(--space-4);">
724Frequently asked</h3> 725 726 <div id="faq-list"> 727 ${ba.map((e,a)=>` 728 <div class="faq-item" id="faq-${a}"> 729 <button class="faq-question" data-faq="${a}"> 730 <span>${e.question}</span> 731 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg> 732 </button> 733 <div class="faq-answer"> 734 <p>${e.answer}</p> 735 </div> 736 </div> 737 `).join("")} 738 </div> 739 </div> 740 </div> 741 </div> 742 </section> 743 `}function Ea(){vt(),_a(),xa()}function _a(){document.querySelectorAll(".faq-question").forEach(e=>{e.addEventListener("click",()=>{const a=e.closest(".faq-item"),t=a.classList.contains("open");document.querySelectorAll(".faq-item").forEach(n=>n.classList.remove("open")),t||a.classList.add("open")})})}function xt(e,a){const t=document.getElementById("form-card-container");if(!t)return;const n=a?`#/questionnaire?id=${a}`:"#/questionnaire";t.innerHTML=` 744 <div class="booking-success-container" style="text-align: center; padding: var(--space-4) 0; animation: rl-rise 0.5s cubic-bezier(0.22, 1, 0.36, 1) forwards;"> 745 <div class="success-icon" style="font-size: 3.2rem; color: #10b981; margin-bottom: var(--space-4);">â</div> 746 <h3 style="margin-bottom: var(--space-3); font-size: 1.8rem; font-weight: 800; background: var(--grad-text); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; font-family: var(--font-heading);">Intake Submitted!</h3> 747 <p style="color: var(--text-dim); margin-bottom: var(--space-6); font-size: var(--text-base); max-width: 550px; margin-left: auto; margin-right: auto; line-height: var(--leading-normal);"> 748 Thanks, ${e||"there"}! Your contact details are saved. Let's get you ready for your discovery call: 749 </p> 750 751 <!-- Step Guidance Banner --> 752 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); max-width: 650px; margin: 0 auto var(--space-8) auto; text-align: left;"> 753 <div style="background: var(--bg-alt); padding: var(--space-4); border-radius: var(--radius-md); border-left: 3px solid var(--purple);"> 754 <strong style="color: var(--purple); font-size: var(--text-sm); text-transform: uppercase;">Step 1: Book Your Call</strong> 755 <p style="font-size: 13px; color: var(--muted); margin-top: 4px;">Choose a date and time on the scheduler below that works for you.</p> 756 </div> 757 <div style="background: var(--bg-alt); padding: var(--space-4); border-radius: var(--radius-md); border-left: 3px solid var(--cyan); display: flex; flex-direction: column; justify-content: space-between;"> 758 <div> 759 <strong style="color: var(--cyan-dark); font-size: var(--text-sm); text-transform: uppercase;">Step 2: Tell Us About Your Business</strong> 760 <p style="font-size: 13px; color: var(--muted); margin-top: 4px;">Fill out the 20-question profile form so we can prepare before we chat.</p> 761 </div> 762 <a href="${n}" class="btn btn--primary" style="margin-top: var(--space-3); width: 100%; text-align: center; padding: 8px 12px; font-size: 13px; background: var(--grad-brand); border: none; text-decoration: none; color: #fff; font-weight: 600; display: inline-block; border-radius: var(--radius-md); cursor: pointer;"> 763 ð Fill Business Profile 764 </a> 765 </div> 766 </div> 767 768 <div class="calendar-embed-wrapper" style="border-radius: var(--radius-lg); overflow: hidden; border: 1px solid var(--border); box-shadow: var(--shadow-sm); background: #fff; margin-bottom: var(--space-6);"> 769 <iframe src="https://calendar.google.com/calendar/appointments/schedules/AcZssZ3SXITjcIytE6aYcvq7OZ9y5Hqhymv3imkt9O4RHuMKlGL7NFsBy4UD99T6ytcuTDy5A3Ebbgcg?gv=true" style="border: 0" width="100%" height="650" frameborder="0"></iframe> 770 </div> 771 772 <div style="margin-top: var(--space-4);"> 773 <p style="font-size: var(--text-sm); color: var(--muted); margin-bottom: var(--space-2);"> 774 Trouble viewing the scheduler? 775 </p> 776 <div style="display: flex; gap: var(--space-3); justify-content: center; align-items: center;"> 777 <a href="https://calendar.app.google/mT6GLLudB4xZryZt7" target="_blank" rel="noopener noreferrer" class="btn btn--secondary btn--md" style="text-decoration: none; display: inline-block;"> 778 Open Calendar in New Tab 779 </a> 780 <a href="${n}" class="btn btn--secondary btn--md" style="text-decoration: none; display: inline-block;"> 781 Go directly to Business Profile 782 </a> 783 </div> 784 </div> 785 </div> 786 `}function xa(){const e=document.getElementById("contact-form");e&&e.addEventListener("submit",async a=>{a.preventDefault();const t=document.getElementById("submit-text"),n=document.getElementById("submit-loading"),o=document.getElementById("contact-submit"),s=document.getElementById("form-status");t.style.display="none",n.style.display="inline",o.disabled=!0,s.className="form-status";const i=new FormData(e),r=Object.fromEntries(i.entries());try{const d={email:r.email,name:r.name,company:r.company,service:r.service,message:r.message},l=await fetch(`${O}/intakes/submit`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d)});if(l.ok){const c=await l.json();xt(r.name,c.clientId)}else throw new Error("Backend failed")}catch{try{if((await fetch("https://formsubmit.co/ajax/[email protected]",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(r)})).ok)xt(r.name,null);else throw new Error("Both failed")}catch{s.textContent="Something went wrong. Please email us directly at [email protected]",s.className="form-status form-status--error",t.style.display="inline",n.style.display="none",o.disabled=!1}}})}const le={dateLabel:"July 30â31, 2026",dayLabel:"Thursday & Friday · 2 full days",location:"DallasâFort Worth, TX",seats:30,deadlineLabel:"June 30",serviceTag:"AI Workshop · July 30â31, 2026"},ka=[{icon:"ð§ ",title:"Operating-system thinking",desc:"The framework behind Crystal â how to run your entire business from one place instead of a dozen tools."},{icon:"ðï¸",title:"Databases & a single source of truth",desc:"Ground your whole business in one database so your data lives in one place â not scattered across apps and spreadsheets."},{icon:"ð¤",title:"AI agents that do the work",desc:"Build agents that handle follow-up, quoting, and scheduling â not just chatbots that answer questions."},{icon:"ð",title:"Scaling vertically with AI",desc:"Stack AI across your operation to grow output and revenue without growing headcount."},{icon:"ð®",title:"Hands-on with Crystal",desc:"Test the Crystal operating system live and walk out with i
786deas to inspire your own build."},{icon:"ð¤",title:"Connections with other entrepreneurs",desc:"Two days in a room of operators wrestling with the same growth problems you are."},{icon:"ð",title:"A community after the course",desc:"Leave with relationships and an ongoing community of like-minded people looking to hyperscale together."}];function Ma(){const e=le,a=t=>`<span style="display:inline-flex; align-items:center; gap:8px; font-size:13px; font-weight:600; color:var(--ink); background:var(--bg-alt); border:1px solid var(--border); padding:8px 14px; border-radius:12px;">${t}</span>`;return` 787 <style> 788 @keyframes wkPulse { 789 0% { box-shadow: 0 0 0 0 rgba(253,224,71,0.7); } 790 70% { box-shadow: 0 0 0 10px rgba(253,224,71,0); } 791 100% { box-shadow: 0 0 0 0 rgba(253,224,71,0); } 792 } 793 @media (max-width: 760px) { 794 #workshop .workshop-card { grid-template-columns: 1fr !important; } 795 #workshop .workshop-card__pitch, 796 #workshop .workshop-card__form { padding: var(--space-8) var(--space-5) !important; } 797 } 798 </style> 799 800 <!-- HERO --> 801 <section class="hero hero--page hero--compact" id="workshop-hero"> 802 <div class="hero__grid hero__grid--center"> 803 <div class="hero__copy"> 804 <span class="eyebrow eyebrow--center reveal">Live ${e.dayLabel.includes("2")?"2-Day":"Multi-Day"} Event · Limited Seats</span> 805 <h1 class="hero__title reveal reveal-delay-1" style="margin-top:var(--space-4);">The BizWrap <span class="grad-text">AI Workshop</span></h1> 806 <p class="hero__subtitle reveal reveal-delay-2"> 807 Two hands-on days learning to put AI to work <em>inside</em> your business â ${e.dateLabel}. Seats are limited, so this starts as a waitlist: once enough people commit, we lock in the venue and you're first in line for a ticket. 808 </p> 809 <div class="reveal reveal-delay-2" style="display:flex; flex-wrap:wrap; gap:10px; justify-content:center; margin-top:var(--space-6);"> 810 ${a("ð "+e.dateLabel)} 811 ${a("ðï¸ "+e.dayLabel)} 812 ${a("ð "+e.location)} 813 </div> 814 </div> 815 </div> 816 </section> 817 818 <!-- CARD: pitch + signup form --> 819 <section class="section" id="workshop" style="padding-top:var(--space-4); padding-bottom:var(--space-16);"> 820 <div class="container"> 821 <div class="workshop-card reveal" style=" 822 display:grid; grid-template-columns:1.05fr 0.95fr; gap:0; 823 border-radius:var(--radius-xl, 26px); overflow:hidden; 824 border:1px solid var(--border); box-shadow:var(--shadow-lg, 0 24px 60px rgba(16,16,40,0.18));"> 825 826 <!-- LEFT: value + scarcity (dark gradient) --> 827 <div class="workshop-card__pitch" style=" 828 position:relative; padding:var(--space-10) var(--space-8); 829 background:linear-gradient(150deg, #15102e 0%, #1d1340 45%, #221a4d 100%); color:#fff; overflow:hidden;"> 830 <div aria-hidden="true" style="position:absolute; top:-90px; right:-90px; width:280px; height:280px; border-radius:50%; background:radial-gradient(circle, rgba(124,58,237,0.45), transparent 70%); filter:blur(8px);"></div> 831 <div aria-hidden="true" style="position:absolute; bottom:-110px; left:-70px; width:260px; height:260px; border-radius:50%; background:radial-gradient(circle, rgba(1,199,204,0.30), transparent 70%);"></div> 832 833 <div style="position:relative;"> 834 <h2 style="margin:0 0 var(--space-5); font-size:clamp(1.5rem, 3vw, 1.9rem); line-height:1.15; color:#fff;">What you'll walk away with</h2> 835 836 <div style="display:grid; gap:var(--space-4); margin-bottom:var(--space-6);"> 837 ${ka.map(t=>` 838 <div style="display:flex; gap:14px; align-items:flex-start;"> 839 <span style="font-size:1.4rem; line-height:1;">${t.icon}</span> 840 <div> 841 <div style="font-weight:700; color:#fff; font-size:15px; margin-bottom:2px;">${t.title}</div> 842 <div style="font-size:13.5px; color:rgba(255,255,255,0.74); line-height:1.5;">${t.desc}</div> 843 </div> 844 </div> 845 `).join("")} 846 </div> 847 848 <!-- scarcity bar --> 849 <div style="background:rgba(0,0,0,0.22); border:1px solid rgba(255,255,255,0.12); border-radius:var(--radius-lg, 18px); padding:var(--space-4) var(--space-5);"> 850 <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;">
851 <span style="font-size:12px; font-weight:800; letter-spacing:0.05em; text-transform:uppercase; color:#fff; display:inline-flex; align-items:center; gap:8px;"> 852 <span style="width:8px; height:8px; border-radius:50%; background:#fde047; box-shadow:0 0 0 0 rgba(253,224,71,0.7); animation:wkPulse 2s infinite;"></span> 853 ðª Only ${e.seats} seats 854 </span> 855 <span style="font-size:11px; font-weight:800; letter-spacing:0.05em; text-transform:uppercase; color:#0f1020; background:#fde047; padding:4px 10px; border-radius:999px;">Filling fast</span> 856 </div> 857 <div style="height:8px; border-radius:999px; background:rgba(255,255,255,0.14); overflow:hidden;"> 858 <div style="height:100%; width:72%; border-radius:999px; background:linear-gradient(90deg, #7c3aed, #01c7cc);"></div> 859 </div> 860 <p style="margin:10px 0 0; font-size:12.5px; color:rgba(255,255,255,0.72); line-height:1.5;"> 861 Seats are first-come, first-seated. Waitlist closes <strong style="color:#fff;">${e.deadlineLabel}</strong> â confirmed signups get the time, exact location, and their ticket by then. 862 </p> 863 </div> 864 </div> 865 </div> 866 867 <!-- RIGHT: the form (white) --> 868 <div class="workshop-card__form" style="padding:var(--space-10) var(--space-8); background:#fff;"> 869 <h3 style="margin-bottom:6px;">Save my seat</h3> 870 <p style="color:var(--muted); font-size:var(--text-sm); margin-bottom:var(--space-6);"> 871 No payment now â just claim your spot on the list. We'll email your ticket & the details by ${e.deadlineLabel}. 872 </p> 873 874 <form id="workshop-form"> 875 <div class="form-group"> 876 <label class="form-label" for="ws-name">Full name</label> 877 <input class="form-input" type="text" id="ws-name" name="name" placeholder="Joey Murphy" required> 878 </div> 879 880 <div class="form-group"> 881 <label class="form-label" for="ws-email">Email address</label> 882 <input class="form-input" type="email" id="ws-email" name="email" placeholder="[email protected]" required> 883 </div> 884 885 <div style="display:grid; grid-template-columns:1fr 1fr; gap:var(--space-4);"> 886 <div class="form-group"> 887 <label class="form-label" for="ws-phone">Mobile (for your ticket)</label> 888 <input class="form-input" type="tel" id="ws-phone" name="phone" placeholder="(555) 123-4567"> 889 </div> 890 <div class="form-group"> 891 <label class="form-label" for="ws-seats">How many seats?</label> 892 <select class="form-select" id="ws-seats" name="seats"> 893 <option value="1" selected>Just me (1)</option> 894 <option value="2">2 seats</option> 895 <option value="3">3 seats</option> 896 <option value="4+">4+ (bringing my team)</option> 897 </select> 898 </div> 899 </div> 900 901 <div class="form-group"> 902 <label class="form-label" for="ws-company">Company <span style="color:var(--muted-light); font-weight:500;">(optional)</span></label> 903 <input class="form-input" type="text" id="ws-company" name="company" placeholder="Your business"> 904 </div> 905 906 <div class="form-group"> 907 <label class="form-label" for="ws-goal">What do you want to walk away with? <span style="color:var(--muted-light); font-weight:500;">(optional)</span></label> 908 <textarea class="form-textarea" id="ws-goal" name="goal" style="min-height:90px;" placeholder="e.g. AI agents for my sales follow-up, or just to understand what's possible"></textarea> 909 </div> 910 911 <button class="btn btn--primary btn--lg" type="submit" id="ws-submit" style="width:100%;">
912 <span id="ws-submit-text">ðï¸ Claim my seat on the waitlist</span> 913 <span id="ws-submit-loading" style="display:none;">Reserving your spotâ¦</span> 914 </button> 915 916 <div id="ws-form-status" class="form-status"></div> 917 <p style="text-align:center; color:var(--muted-light); font-size:12px; margin-top:var(--space-4); line-height:1.5;"> 918 Signing up is free and non-binding â it just puts you on the list. If we hit enough committed attendees, the event is on and you'll be first to get a ticket. 919 </p> 920 </form> 921 </div> 922 </div> 923 </div> 924 </section> 925 `}function Ta(){vt(),Sa()}function Sa(){const e=document.getElementById("workshop-form");e&&e.addEventListener("submit",async a=>{a.preventDefault();const t=document.getElementById("ws-submit-text"),n=document.getElementById("ws-submit-loading"),o=document.getElementById("ws-submit"),s=document.getElementById("ws-form-status");t.style.display="none",n.style.display="inline",o.disabled=!0,s.className="form-status";const i=Object.fromEntries(new FormData(e).entries()),r=i.seats||"1",d=(i.goal||"").trim(),l=`ðï¸ AI WORKSHOP WAITLIST â ${le.dateLabel} 926Seats requested: ${r} 927Goal: ${d||"â"} 928 929(Joined via the AI Workshop page waitlist. Send time, exact location & ticket by ${le.deadlineLabel} if confirmed.)`,c={email:i.email,name:i.name,company:i.company,phone:i.phone,service:le.serviceTag,source:"workshop-waitlist",message:l};try{if(!(await fetch(`${O}/intakes/submit`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(c)})).ok)throw new Error("Backend failed");kt(i.name)}catch{try{if(!(await fetch("https://formsubmit.co/ajax/[email protected]",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({_subject:`AI Workshop waitlist: ${i.name||"New signup"}`,...c})})).ok)throw new Error("Both failed");kt(i.name)}catch{s.textContent="Something went wrong. Please email us directly at [email protected]",s.className="form-status form-status--error",t.style.display="inline",n.style.display="none",o.disabled=!1}}})}function kt(e){const a=document.querySelector(".workshop-card__form");a&&(a.innerHTML=` 930 <div style="text-align:center; padding:var(--space-4) 0; animation:rl-rise 0.5s cubic-bezier(0.22,1,0.36,1) forwards;"> 931 <div style="font-size:3rem; line-height:1; margin-bottom:var(--space-4);">ðï¸</div> 932 <h3 style="margin-bottom:var(--space-3); font-size:1.7rem; font-weight:800; background:var(--grad-text); -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text; font-family:var(--font-heading);"> 933 You're on the list${e?", "+e.split(" ")[0]:""}! 934 </h3> 935 <p style="color:var(--muted); font-size:var(--text-base); line-height:1.6; max-width:42ch; margin:0 auto var(--space-6);"> 936 Your seat for the <strong>${le.dateLabel}</strong> AI Workshop is reserved on the waitlist. The moment we confirm enough attendees, we'll email you the time, the exact ${le.location} location, and your ticket â by <strong>${le.deadlineLabel}</strong>. 937 </p> 938 939 <div style="display:grid; gap:10px; text-align:left; max-width:420px; margin:0 auto var(--space-6);"> 940 <div style="display:flex; gap:12px; align-items:flex-start; background:var(--bg-alt); border:1px solid var(--border); border-radius:var(--radius-md); padding:var(--space-3) var(--space-4);"> 941 <span style="font-size:1.1rem;">â </span> 942 <span style="font-size:13px; color:var(--ink); line-height:1.5;"><strong>Spot held.</strong> Seats are limited to ${le.seats} â you're in line.</span> 943 </div> 944 <div style="display:flex; gap:12px; align-items:flex-start; background:var(--bg-alt); border:1px solid var(--border); border-radius:var(--radius-md); padding:var(--space-3) var(--space-4);"> 945 <span style="font-size:1.1rem;">ð¨</span> 946 <span style="font-size:13px; color:var(--ink); line-height:1.5;"><strong>Watch your inbox.</strong> Details & your ticket arrive by ${le.deadlineLabel}
946.</span> 947 </div> 948 <div style="display:flex; gap:12px; align-items:flex-start; background:var(--bg-alt); border:1px solid var(--border); border-radius:var(--radius-md); padding:var(--space-3) var(--space-4);"> 949 <span style="font-size:1.1rem;">ð£</span> 950 <span style="font-size:13px; color:var(--ink); line-height:1.5;"><strong>Know someone who'd love this?</strong> Send them here â more committed seats means it's locked in sooner.</span> 951 </div> 952 </div> 953 954 <a href="#/contact" class="btn btn--secondary btn--md" style="text-decoration:none; display:inline-block;"> 955 Want a full system too? Book a demo â 956 </a> 957 </div> 958 `)}function _e(e){return e?e[0]:null}function ze(e){return e?e[1]:null}function tt(e){return e==="white"?"black":"white"}function ft(e){return e==="white"?"w":"b"}function de(e,a){return e>=0&&e<8&&a>=0&&a<8}function qt(e){return e.map(a=>[...a])}function Ha(e){return{board:qt(e.board),turn:e.turn,moveHistory:[...e.moveHistory],captured:{white:[...e.captured.white],black:[...e.captured.black]},castlingRights:{...e.castlingRights},enPassantTarget:e.enPassantTarget?{...e.enPassantTarget}:null,halfMoveClock:e.halfMoveClock,fullMoveNumber:e.fullMoveNumber,gameOver:e.gameOver,result:e.result}}function Qe(){return{board:[["bR","bN","bB","bQ","bK","bB","bN","bR"],["bP","bP","bP","bP","bP","bP","bP","bP"],[null,null,null,null,null,null,null,null],[null,null,null,null,null,null,null,null],[null,null,null,null,null,null,null,null],[null,null,null,null,null,null,null,null],["wP","wP","wP","wP","wP","wP","wP","wP"],["wR","wN","wB","wQ","wK","wB","wN","wR"]],turn:"white",moveHistory:[],captured:{white:[],black:[]},castlingRights:{wK:!0,wQ:!0,bK:!0,bQ:!0},enPassantTarget:null,halfMoveClock:0,fullMoveNumber:1,gameOver:!1,result:null}}function we(e,a,t,n){const o=ft(n),s=[[-2,-1],[-2,1],[-1,-2],[-1,2],[1,-2],[1,2],[2,-1],[2,1]];for(const[c,p]of s){const u=a+c,g=t+p;if(de(u,g)&&e[u][g]===o+"N")return!0}const i=[[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]];for(const[c,p]of i){const u=a+c,g=t+p;if(de(u,g)&&e[u][g]===o+"K")return!0}const r=n==="white"?1:-1;for(const c of[-1,1]){const p=a+r,u=t+c;if(de(p,u)&&e[p][u]===o+"P")return!0}const d=[[-1,0],[1,0],[0,-1],[0,1]];for(const[c,p]of d){let u=a+c,g=t+p;for(;de(u,g);){const y=e[u][g];if(y){if(y===o+"R"||y===o+"Q")return!0;break}u+=c,g+=p}}const l=[[-1,-1],[-1,1],[1,-1],[1,1]];for(const[c,p]of l){let u=a+c,g=t+p;for(;de(u,g);){const y=e[u][g];if(y){if(y===o+"B"||y===o+"Q")return!0;break}u+=c,g+=p}}return!1}function zt(e,a){const t=ft(a)+"K";for(let n=0;n<8;n++)for(let o=0;o<8;o++)if(e[n][o]===t)return{row:n,col:o};return null}function ve(e,a){const t=zt(e.board,a);return t?we(e.board,t.row,t.col,tt(a)):!1}function Dt(e,a){for(let t=0;t<8;t++)for(let n=0;n<8;n++)if(e.board[t][n]&&_e(e.board[t][n])===ft(a)&&Ne(e,t,n).length>0)return!0;return!1}function pt(e,a){return ve(e,a)&&!Dt(e,a)}function Ra(e,a){return!ve(e,a)&&!Dt(e,a)}function Ca(e,a,t){const n=e.board[a][t];if(!n)return[];const o=_e(n),s=ze(n),i=[];function r(l,c){if(!de(l,c))return!1;const p=e.board[l][c];return p&&_e(p)===o?!1:(i.push({row:l,col:c}),!p)}function d(l){for(const[c,p]of l){let u=a+c,g=t+p;for(;de(u,g)&&r(u,g);)u+=c,g+=p}}switch(s){case"P":{const l=o==="w"?-1:1,c=o==="w"?6:1;de(a+l,t)&&!e.board[a+l][t]&&(i.push({row:a+l,col:t}),a===c&&!e.board[a+2*l][t]&&i.push({row:a+2*l,col:t}));for(const p of[-1,1]){const u=a+l,g=t+p;if(de(u,g)){const y=e.board[u][g];y&&_e(y)!==o&&i.push({row:u,col:g}),e.enPassantTarget&&e.enPassantTarget.row===u&&e.enPassantTarget.col===g&&i.push({row:u,col:g})}}break}case"N":{const l=[[-2,-1],[-2,1],[-1,-2],[-1,2],[1,-2],[1,2],[2,-1],[2,1]];for(const[c,p]of l)r(a+c,t+p);break}case"B":d([[-1,-1],[-1,1],[1,-1],[1,1]]);break;case"R":d([[-1,0],[1,0],[0,-1],[0,1]]);break;case"Q":d([[-1,-1],[-1,1],[1,-1],[1,1],[-1,0],[1,0],[0,-1],[0,1]]);break;case"K":{const l=[[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]];for(const[u,g]of l)r(a+u,t+g);const c=o==="w"?"black":"white",p=o==="w"?7:0;a===p&&t===4&&!we(e.board,a,t,c)&&(e.castlingRights[o+"K"]&&!e.board[p][5]&&!e.board[p][6]&&e.board[p][7]===o+"R"&&!we(e.board,p,5,c)&&!we(e.board,p,6,c)&&i.push({row:p,col:6}),e.castlingRights[o+"Q"]&&!e.board[p][3]&&!e.board[p][2]&&!e.board[p][1]&&e.board[p][0]===o+"R"&&!we(e.board,p,3,c)&&!we(e.board,p,2,c)&&i.push({row:p,col:2}));break}}return i}function Ne(e,a,t){const n=e.board[a][t];
958if(!n)return[];const o=_e(n),s=o==="w"?"white":"black";return s!==e.turn?[]:Ca(e,a,t).filter(r=>{const d=qt(e.board);if(d[r.row][r.col]=d[a][t],d[a][t]=null,ze(n)==="P"&&e.enPassantTarget&&r.row===e.enPassantTarget.row&&r.col===e.enPassantTarget.col){const c=o==="w"?r.row+1:r.row-1;d[c][r.col]=null}if(ze(n)==="K"&&Math.abs(r.col-t)===2){const c=o==="w"?7:0;r.col===6?(d[c][5]=d[c][7],d[c][7]=null):r.col===2&&(d[c][3]=d[c][0],d[c][0]=null)}const l=zt(d,s);return l?!we(d,l.row,l.col,tt(s)):!1})}function Ue(e){return"abcdefgh"[e]}function st(e){return String(8-e)}function Ia(e,a,t,n,o,s,i){const r=e.board[a][t],d=ze(r),c=_e(r)==="w"?"white":"black",p=tt(c);if(d==="K"&&Math.abs(o-t)===2){const y=o===6?"O-O":"O-O-O";let m="";return pt(i,p)?m="#":ve(i,p)&&(m="+"),y+m}let u="";const g=e.board[n][o]!==null||d==="P"&&o!==t;if(d!=="P"){u+=d;const y=[];for(let m=0;m<8;m++)for(let v=0;v<8;v++)m===a&&v===t||e.board[m][v]===r&&Ne(e,m,v).some(b=>b.row===n&&b.col===o)&&y.push({row:m,col:v});if(y.length>0){const m=y.some(T=>T.col===t),v=y.some(T=>T.row===a);m?v?u+=Ue(t)+st(a):u+=st(a):u+=Ue(t)}}else g&&(u+=Ue(t));return g&&(u+="x"),u+=Ue(o)+st(n),s&&(u+="="+s),pt(i,p)?u+="#":ve(i,p)&&(u+="+"),u}function at(e,a,t,n,o,s){const i=Ha(e),r=i.board[a][t];if(!r)return e;const d=_e(r),l=ze(r),c=d==="w"?"white":"black",p=i.board[n][o];if(p&&i.captured[c].push(p),l==="P"&&i.enPassantTarget&&n===i.enPassantTarget.row&&o===i.enPassantTarget.col){const y=d==="w"?n+1:n-1,m=i.board[y][o];m&&i.captured[c].push(m),i.board[y][o]=null}if(l==="K"&&Math.abs(o-t)===2){const y=d==="w"?7:0;o===6?(i.board[y][5]=i.board[y][7],i.board[y][7]=null):o===2&&(i.board[y][3]=i.board[y][0],i.board[y][0]=null)}if(i.board[n][o]=r,i.board[a][t]=null,l==="P"&&n===(d==="w"?0:7)){const m=s||"Q";i.board[n][o]=d+m}if(l==="P"&&Math.abs(n-a)===2){const y=(a+n)/2;i.enPassantTarget={row:y,col:t}}else i.enPassantTarget=null;l==="K"&&(i.castlingRights[d+"K"]=!1,i.castlingRights[d+"Q"]=!1),l==="R"&&(a===7&&t===0&&(i.castlingRights.wQ=!1),a===7&&t===7&&(i.castlingRights.wK=!1),a===0&&t===0&&(i.castlingRights.bQ=!1),a===0&&t===7&&(i.castlingRights.bK=!1)),n===7&&o===0&&(i.castlingRights.wQ=!1),n===7&&o===7&&(i.castlingRights.wK=!1),n===0&&o===0&&(i.castlingRights.bQ=!1),n===0&&o===7&&(i.castlingRights.bK=!1),l==="P"||p?i.halfMoveClock=0:i.halfMoveClock++;const u=Ia(e,a,t,n,o,l==="P"&&(n===0||n===7)?s||"Q":null,i);i.moveHistory.push(u),c==="black"&&i.fullMoveNumber++,i.turn=tt(c);const g=i.turn;return pt(i,g)?(i.gameOver=!0,i.result={type:"checkmate",winner:c}):Ra(i,g)?(i.gameOver=!0,i.result={type:"stalemate",winner:null}):i.halfMoveClock>=100&&(i.gameOver=!0,i.result={type:"draw",reason:"50-move rule",winner:null}),i}const $a={P:100,N:320,B:330,R:500,Q:900,K:2e4},La=[0,0,0,0,0,0,0,0,50,50,50,50,50,50,50,50,10,10,20,30,30,20,10,10,5,5,10,25,25,10,5,5,0,0,0,20,20,0,0,0,5,-5,-10,0,0,-10,-5,5,5,10,10,-20,-20,10,10,5,0,0,0,0,0,0,0,0],Ba=[-50,-40,-30,-30,-30,-30,-40,-50,-40,-20,0,0,0,0,-20,-40,-30,0,10,15,15,10,0,-30,-30,5,15,20,20,15,5,-30,-30,0,15,20,20,15,0,-30,-30,5,10,15,15,10,5,-30,-40,-20,0,5,5,0,-20,-40,-50,-40,-30,-30,-30,-30,-40,-50],Pa=[-20,-10,-10,-10,-10,-10,-10,-20,-10,0,0,0,0,0,0,-10,-10,0,10,10,10,10,0,-10,-10,5,5,10,10,5,5,-10,-10,0,5,10,10,5,0,-10,-10,5,5,5,5,5,5,-10,-10,5,0,0,0,0,5,-10,-20,-10,-10,-10,-10,-10,-10,-20],Aa=[0,0,0,0,0,0,0,0,5,10,10,10,10,10,10,5,-5,0,0,0,0,0,0,-5,-5,0,0,0,0,0,0,-5,-5,0,0,0,0,0,0,-5,-5,0,0,0,0,0,0,-5,-5,0,0,0,0,0,0,-5,0,0,0,5,5,0,0,0],Ga=[-20,-10,-10,-5,-5,-10,-10,-20,-10,0,0,0,0,0,0,-10,-10,0,5,5,5,5,0,-10,-5,0,5,5,5,5,0,-5,0,0,5,5,5,5,0,-5,-10,5,5,5,5,5,0,-10,-10,0,5,0,0,0,0,-10,-20,-10,-10,-5,-5,-10,-10,-20],qa=[-30,-40,-40,-50,-50,-40,-40,-30,-30,-40,-40,-50,-50,-40,-40,-30,-30,-40,-40,-50,-50,-40,-40,-30,-30,-40,-40,-50,-50,-40,-40,-30,-20,-30,-30,-40,-40,-30,-30,-20,-10,-20,-20,-20,-20,-20,-20,-10,20,20,0,0,0,0,20,20,20,30,10,0,0,10,30,20],za={P:La,N:Ba,B:Pa,R:Aa,Q:Ga,K:qa};function Da(e){let a=0;for(let t=0;t<8;t++)for(let n=0;n<8;n++){const o=e.board[t][n];if(!o)continue;const s=o[1],i=o[0]==="w",r=$a[s]||0,d=za[s],l=i?t*8+n:(7-t)*8+n,c=d?d[l]:0;i?a+=r+c:a-=r+c}return ve(e,e.turn)&&(a+=e.turn==="white"?-50:50),a}function Nt(e,a,t,n,o){if(a===0||e.gameOver)return{score:Da(e),move:null};const s=o?"w":"b";let i=null,r=o?-1/0:1/0;const d=[];for(let l=0;l<8;l++)for(let c=0;c<8;c++){const p=e.board[l][c];if(p&&p[0]===s){const u=Ne(e,l,c);for(const g of u)d.push({from:{row:l,col:c},to:g})}}if(d.length===0)return ve(e,o?"white":"black")?{score:o?-99999+(3-a):99999-(3-a),move:null}:{score:0,move:null};d.sort((l,c)=>{const p=e.board[l.to.row][l.to.col]?1:0;return(e.board[c.to.row][c.to.col]?1:0)-p});for(const l of d){const c=at(e,l.from.row,l.from.col,l.to.row,l.to.col),p=Nt(c,a-1,t,n,!o);if(o?(p.score>r&&(r=p.score,i=l),t=Math.max(t,r)):(p.score<r&&(r=p.score,i=l),n=Math.min(n,r)),n<=t)break}return{score:r,move:i}}function Na(e,a=3){if(e.gameOver)return null;const t=e.turn==="white",n=Nt(e,a,-1/0,1/0,t);if(!n.move)return null;const o=e.board[n.move.from.row][n.move.from.col];let s;if(o&&o[1]==="P"){const i=o[0]==="w"?0:7;n.move.to.row===i&&(s="Q")}return{from:n.move.from,to:n.move.to,promotion:s}}
958let Y=null,$=null,A=null,te="",X="",ke="",U=null,se=[],K="lobby",Le=[],j=!1,ue="medium",ae=!1,Xe=!1,Be=null,Pe=null;const He={wK:"â",wQ:"â",wR:"â",wB:"â",wN:"â",wP:"â",bK:"â",bQ:"â",bR:"â",bB:"â",bN:"â",bP:"â"},Mt={P:1,N:3,B:3,R:5,Q:9,K:0};let N=null,Ze=!1;function Oa(){return new Promise((e,a)=>{if(window.THREE){Ze=!0,e();return}const t=document.createElement("script");t.src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js",t.onload=()=>{Ze=!0,e()},t.onerror=a,document.head.appendChild(t)})}class Fa{constructor(a){this.container=a;const t=a.clientWidth||800,n=window.innerHeight-100,o=Math.max(400,Math.min(t*.88,n));a.style.height=o+"px",this.scene=new THREE.Scene,this.camera=new THREE.PerspectiveCamera(50,t/o,.1,100),this.camera.position.set(0,10,9),this.camera.lookAt(0,0,.5),this.renderer=new THREE.WebGLRenderer({antialias:!0,alpha:!0}),this.renderer.setSize(t,o),this.renderer.setPixelRatio(Math.min(window.devicePixelRatio,2)),this.renderer.shadowMap.enabled=!0,this.renderer.shadowMap.type=THREE.PCFSoftShadowMap,this.renderer.toneMapping=THREE.ACESFilmicToneMapping,this.renderer.toneMappingExposure=1.3,this.renderer.domElement.style.borderRadius="16px",this.renderer.domElement.style.cursor="pointer",a.appendChild(this.renderer.domElement),this.pieces={},this.squareMeshes=[],this.hlMeshes=[],this.dragging=null,this.dragStartPos=null,this.dragStartSq=null,this.dragPlane=new THREE.Plane(new THREE.Vector3(0,1,0),0),this.raycaster=new THREE.Raycaster,this.mouse=new THREE.Vector2,this.animQueue=[],this.theme="classic",this._orbitTarget=new THREE.Vector3(0,0,.5);const s=new THREE.Vector3().subVectors(this.camera.position,this._orbitTarget);this._orbitRadius=s.length(),this._orbitTheta=Math.atan2(s.x,s.z),this._orbitPhi=Math.acos(Math.max(-1,Math.min(1,s.y/this._orbitRadius))),this._orbiting=!1;let i=null,r=null;try{i=this._makeBumpMap(.12,"marble"),r=this._makeBumpMap(.15,"crystal")}catch{}this.wMat=new THREE.MeshStandardMaterial({color:16116438,roughness:.22,metalness:.08,...i?{bumpMap:i,bumpScale:.015}:{},envMapIntensity:.6}),this.bMat=new THREE.MeshStandardMaterial({color:7233428,roughness:.2,metalness:.14,...r?{bumpMap:r,bumpScale:.012}:{},envMapIntensity:.8}),this._lights(),this._board(),this._edgeMesh=this.scene.children.find(d=>d.geometry?.parameters?.width===8.8),this._edgeMesh&&(this._edgeMesh.userData.type="edge"),this._slabMesh=this.scene.children.find(d=>d.geometry?.parameters?.width===8.6),this._slabMesh&&(this._slabMesh.userData.type="edge"),this._events(),this._loop(),this._ro=new ResizeObserver(()=>this._resize()),this._ro.observe(a)}_makeBumpMap(a,t){const o=document.createElement("canvas");o.width=256,o.height=256;const s=o.getContext("2d");s.fillStyle="#808080",s.fillRect(0,0,256,256);const i=s.getImageData(0,0,256,256),r=i.data;for(let l=0;l<r.length;l+=4){const c=l/4%256,p=Math.floor(l/4/256);let u=128;if(t==="marble"){const g=Math.sin(c*.05+p*.03+Math.sin(p*.08)*4)*.5+.5,y=(Math.random()-.5)*30;u=128+g*a*200+y}else{const g=Math.sin(c*.12)*Math.cos(p*.12)*.5+.5,y=(Math.random()-.5)*25;u=128+g*a*180+y}r[l]=r[l+1]=r[l+2]=Math.max(0,Math.min(255,u))}s.putImageData(i,0,0);const d=new THREE.CanvasTexture(o);return d.wrapS=d.wrapT=THREE.RepeatWrapping,d.repeat.set(2,2),d}_lights(){this.scene.add(new THREE.AmbientLight(16777215,.45));const a=new THREE.DirectionalLight(16777215,1);a.position.set(-4,10,5),a.castShadow=!0,a.shadow.mapSize.set(2048,2048),a.shadow.camera.near=1,a.shadow.camera.far=25,a.shadow.camera.left=-6,a.shadow.camera.right=6,a.shadow.camera.top=6,a.shadow.camera.bottom=-6,a.shadow.bias=-8e-4,this.scene.add(a);const t=new THREE.DirectionalLight(8961023,.3);t.position.set(5,6,-3),this.scene.add(t);const n=new THREE.DirectionalLight(16768460,.2);n.position.set(0,4,-8),this.scene.add(n)}_board(){const a=new THREE.Mesh(new THREE.BoxGeometry(8.6,.35,8.6),new THREE.MeshStandardMaterial({color:1468008,roughness:.25,metalness:.15}));a.position.y=-.175,a.receiveShadow=!0,a.castShadow=!0,this.scene.add(a);const t=new THREE.Mesh(new THREE.BoxGeometry(8.8,.38,8.8),new THREE.MeshStandardMaterial({color:936524,roughness:.4,metalness:.1}));t.position.y=-.19,this.scene.add(t);const n=new THREE.MeshStandardMaterial({color:16054264,roughness:.5,metalness:.02}),o=new THREE.MeshStandardMaterial({color:3126712,roughness:.35,metalness:.05});for(let s=0;s<8;s++)for(let i=0;i<8;i++){const r=(s+i)%2===1,d=new THREE.Mesh(new THREE.BoxGeometry(1,.06,1),(r?n:o).clone());d.position.set(i-3.5,.001,s-3.5),d.receiveShadow=!0,d.userData={type:"sq",row:s,col:i,origColor:d.material.color.getHex()},this.squareMeshes.push(d),this.scene.add(d)}}_prof(a){const t=(n,o)=>new THREE.Vector2(n,o);switch(a){case"P":return[t(0,0),t(.32,0),t(.32,.02),t(.3,.04),t(.28,.06),t(.26,.08),t(.18,.12),t(.16,.14),t(.18,.16),t(.18,.18),t(.16,.2),t(.12,.24),t(.1,.3),t(.09,.34),t(.1,.37),t(.12,.39),t(.12,.41),t(.1,.43),t(.09,.46),t(.08,.5),t(.14,.52),t(.16,.55),t(.16,.58),t(.14,.6),t(.12,.63),t(.14,.66),t(.16,.7),t(.17,.74),t(.17,.78),t(.15,.82),t(.12,.86),t(.08,.9),t(.04,.93),t(0,.95)];case"R":return[t(0,0),t(.34,0),t(.34,.02),t(.32,.04),t(.3,.06),t(.28,.08),t(.2,.12),t(.18,.14),t(.2,.16),t(.2,.18),t(.18,.2),t(.16,.22),t(.15,.26),t(.14,.3),t(.14,.5),t(.15,.54),t(.16,.56),t(.16,.6),t(.15,.62),t(.18,.64),t(.2,.66),t(.2,.68),t(.18,.7),t(.22,.72),t(.22,.74),t(0,.74)];case"N":return[t(0,0),t(.32,0),t(.32,.02),t(.3,.04),t(.28,.06),t(.26,.08),t(.18,.12),t(.16,.14),t(.18,.16),t(.18,.18),t(.16,.2),t(.12,.24),t(.1,.28),t(.09,.32),t(.1,.35),t(.12,.37),t(.12,.39),t(.1,.41),t(.08,.44),t(.07,.48),t(.06,.5),t(0,.5)];case"B":return[t(0,0),t(.32,0),t(.32,.02),t(.3,.04),t(.28,.06),t(.26,.08),t(.18,.12),t(.16,.14),t(.18,.16),t(.18,.18),t(.16,.2),t(.12,.24),t(.1,.28),t(.09,.32),t(.1,.35),t(.12,.37),t(.12,.39),t(.1,.41),t(.08,.44),t(.07,.48),t(.06,.52),t(.1,.55),t(.13,.58),t(.14,.6),t(.14,.64),t(.13,.68),t(.15,.7),t(.16,.74),t(.16,.78),t(.14,.82),t(.12,.86),t(.08,.9),t(.04,.94),t(.02,.97),t(0,.99)];case"Q":return[t(0,0),t(.34,0),t(.34,.02),t(.32,.04),t(.3,.06),t(.28,.08),t(.2,.12),t(.18,.14),t(.2,.16),t(.2,.18),t(.18,.2),t(.14,.24),t(.12,.28),t(.11,.32),t(.1,.38),t(.09,.44),t(.09,.5),t(.1,.54),t(.12,.56),t(.12,.58),t(.1,.6),t(.09,.63),t(.1,.66),t(.12,.68),t(.14,.72),t(.16,.76),t(.18,.8),t(.19,.84),t(.18,.88),t(.2,.9),t(.22,.94),t(.18,.92),t(.14,.96),t(.1,.94),t(.06,.98),t(.04,.96),t(.03,1),t(0,1.02)];case"K":return[t(0,0),t(.36,0),t(.36,.02),t(.34,.04),t(.32,.06),t(.3,.08),t(.22,.12),t(.2,.14),t(.22,.16),t(.22,.18),t(.2,.2),t(.16,.24),t(.14,.28),t(.13,.32),t(.12,.38),t(.11,.44),t(.11,.5),t(.12,.54),t(.14,.56),t(.14,.58),t(.12,.6),t(.11,.63),t(.12,.66),t(.14,.68),t(.16,.72),t(.18,.76),t(.2,.8),t(.21,.84),t(.2,.88),t(.18,.92),t(.16,.94),t(.1,.97),t(.06,.99),t(0,1)]}}
958_addGrooves(a,t,n,o){n.forEach(s=>{const i=new THREE.Mesh(new THREE.TorusGeometry(o,.008,8,32),t);i.rotation.x=Math.PI/2,i.position.y=s,i.castShadow=!0,a.add(i)})}_makeKnightHead(a){const t=new THREE.Group,n=new THREE.Shape;n.moveTo(-.1,0),n.bezierCurveTo(-.12,.15,-.08,.3,-.04,.4),n.bezierCurveTo(-.02,.44,.02,.46,.04,.46),n.lineTo(.1,.44),n.bezierCurveTo(.12,.4,.14,.3,.12,.15),n.bezierCurveTo(.12,.06,.1,0,.1,0),n.lineTo(-.1,0);const o=new THREE.ExtrudeGeometry(n,{depth:.22,bevelEnabled:!0,bevelThickness:.04,bevelSize:.035,bevelOffset:0,bevelSegments:10}),s=new THREE.Mesh(o,a);s.position.set(-.04,.48,-.11),s.rotation.set(0,0,.06),s.castShadow=!0,s.receiveShadow=!0,t.add(s);const i=new THREE.SphereGeometry(.13,24,18),r=new THREE.Mesh(i,a);r.scale.set(1,.8,.75),r.position.set(-.02,.9,0),r.castShadow=!0,t.add(r);const d=new THREE.SphereGeometry(.06,14,10),l=new THREE.Mesh(d,a);l.position.set(-.06,.86,-.08),l.scale.set(1,.8,.6),l.castShadow=!0,t.add(l);const c=l.clone();c.position.set(-.06,.86,.08),t.add(c);const p=new THREE.CapsuleGeometry(.03,.1,6,10),u=new THREE.Mesh(p,a);u.position.set(-.04,.96,0),u.rotation.set(Math.PI/2,0,0),u.castShadow=!0,t.add(u);const g=new THREE.CapsuleGeometry(.055,.18,10,14),y=new THREE.Mesh(g,a);y.position.set(-.18,.78,0),y.rotation.set(0,0,Math.PI*.4),y.castShadow=!0,t.add(y);const m=new THREE.SphereGeometry(.05,12,10),v=new THREE.Mesh(m,a);v.position.set(-.28,.72,0),v.scale.set(1,.65,.85),v.castShadow=!0,t.add(v);const T=new THREE.SphereGeometry(.022,10,8);[-.035,.035].forEach(w=>{const f=new THREE.Mesh(T,a);f.position.set(-.31,.71,w),f.scale.set(.8,.6,1),f.castShadow=!0,t.add(f)});const b=new THREE.MeshStandardMaterial({color:2236962,roughness:.9}),R=new THREE.BoxGeometry(.12,.005,.06),M=new THREE.Mesh(R,b);M.position.set(-.22,.7,0),M.rotation.set(0,0,.35),t.add(M);const k=new THREE.CapsuleGeometry(.04,.14,8,12),_=new THREE.Mesh(k,a);_.position.set(-.14,.74,0),_.rotation.set(0,0,Math.PI*.38),_.castShadow=!0,t.add(_);const I=new THREE.SphereGeometry(.07,12,10),E=new THREE.Mesh(I,a);E.position.set(-.08,.62,0),E.scale.set(.8,1.2,.7),E.castShadow=!0,t.add(E);const h=new THREE.ConeGeometry(.028,.09,8),S=new THREE.Mesh(h,a);S.position.set(.04,1.01,-.045),S.rotation.set(.12,0,-.3),S.castShadow=!0,t.add(S);const x=S.clone();x.position.set(.04,1.01,.045),x.rotation.set(-.12,0,-.3),t.add(x);const C=new THREE.MeshStandardMaterial({color:1118481,roughness:.3,metalness:.5}),H=new THREE.SphereGeometry(.02,12,10);[-.09,.09].forEach(w=>{const f=new THREE.SphereGeometry(.028,10,8),L=new THREE.Mesh(f,a);L.position.set(-.06,.92,w),L.scale.set(.6,.7,.5),t.add(L);const P=new THREE.Mesh(H,C);P.position.set(-.07,.92,w),t.add(P)});for(let w=0;w<8;w++){const f=w/7,L=.032-f*.006,P=new THREE.SphereGeometry(L,10,8),Z=new THREE.Mesh(P,a),fe=.54+f*.42,ge=.06+Math.sin(f*Math.PI*.6)*.025-f*.02;Z.position.set(ge,fe,0),Z.scale.set(.45,1.6,.8),Z.castShadow=!0,t.add(Z)}return t}_makeRookBattlements(a,t){const n=new THREE.Group,o=new THREE.CylinderGeometry(.21,.21,.05,28,1,!0),s=new THREE.Mesh(o,a);s.position.y=t+.025,s.castShadow=!0,n.add(s);const i=new THREE.CylinderGeometry(.21,.21,.02,28),r=new THREE.Mesh(i,a);r.position.y=t,r.castShadow=!0,n.add(r);for(let d=0;d<6;d++){const l=d/6*Math.PI*2,c=new THREE.BoxGeometry(.1,.13,.1),p=new THREE.Mesh(c,a);p.position.set(Math.sin(l)*.19,t+.05+.065,Math.cos(l)*.19),p.rotation.y=-l,p.castShadow=!0,n.add(p)}return n}_makePiece(a){if(this.theme==="alien")return this._makeAlienPiece(a);if(this.theme==="christmas")return this._makeChristmasPiece(a);if(this.theme==="halloween")return this._makeHalloweenPiece(a);const t=a[0]==="w",n=a[1],o=(t?this.wMat:this.bMat).clone();if(n==="N"){const r=new THREE.Mesh(new THREE.LatheGeometry(this._prof("N"),64),o);r.castShadow=!0,r.receiveShadow=!0,this._addGrooves(r,o,[.15,.19,.37,.4],.11);const d=this._makeKnightHead(o);return r.add(d),r.scale.set(1.15,1.15,1.15),r}if(n==="R"){const r=new THREE.Mesh(new THREE.LatheGeometry(this._prof("R"),64),o);r.castShadow=!0,r.receiveShadow=!0,this._addGrooves(r,o,[.15,.19,.37,.55,.65,.69],.15);const d=this._makeRookBattlements(o,.74);return r.add(d),r.scale.set(1.15,1.15,1.15),r}const s=new THREE.Mesh(new THREE.LatheGeometry(this._prof(n),64),o);if(s.castShadow=!0,s.receiveShadow=!0,n==="P"&&this._addGrooves(s,o,[.15,.19,.38,.42],.11),n==="B"&&this._addGrooves(s,o,[.15,.19,.36,.4,.56],.11),n==="Q"&&this._addGrooves(s,o,[.15,.19,.36,.55,.59],.13),n==="K"&&this._addGrooves(s,o,[.15,.19,.36,.55,.59],.14),n==="K"){const r=o.clone(),d=new THREE.Mesh(new THREE.BoxGeometry(.05,.28,.05),r),l=new THREE.Mesh(new THREE.BoxGeometry(.18,.05,.05),r);d.position.y=1+.14,l.position.y=1+.08,d.castShadow=!0,l.castShadow=!0;const c=new THREE.Mesh(new THREE.SphereGeometry(.04,12,12),r);c.position.y=1.02,c.castShadow=!0,s.add(d),s.add(l),s.add(c)}if(n==="Q"){const r=new THREE.Mesh(new THREE.SphereGeometry(.05,16,16),o);r.position.y=1.06,r.castShadow=!0;const d=new THREE.Mesh(new THREE.BoxGeometry(.02,.1,.02),o),l=new THREE.Mesh(new THREE.BoxGeometry(.07,.02,.02),o);d.position.y=1.14,l.position.y=1.12,d.castShadow=!0,l.castShadow=!0,s.add(r),s.add(d),s.add(l)}if(n==="B"){const r=new THREE.Mesh(new THREE.SphereGeometry(.035,14,14),o);r.position.y=1.01,r.castShadow=!0,s.add(r);const d=new THREE.MeshStandardMaterial({color:t?13154464:1315882,roughness:.5,metalness:.05}),l=new THREE.Mesh(new THREE.BoxGeometry(.005,.16,.2),d);l.position.set(0,.84,0),l.rotation.z=Math.PI*.18,s.add(l)}const i=Math.random()*100;return n==="K"?s.userData._animFn=r=>{s.scale.setScalar(1.15+Math.sin(r*1.5+i)*.005)}:n==="Q"?s.userData._animFn=r=>{s.rotation.y+=Math.sin(r*1.2+i)*3e-4}:n==="N"&&(s.userData._animFn=r=>{s.position.y=Math.sin(r*2+i)*.004}),s.scale.set(1.15,1.15,1.15),s}sync(a,t){for(const o in this.pieces)this.scene.remove(this.pieces[o]);this.pieces={};const n=t==="black";for(let o=0;o<8;o++)for(let s=0;s<8;s++){const i=a.board[o][s];if(i)try{const r=this._makePiece(i),d=n?7-o:o,l=n?7-s:s;r.position.set(l-3.5,0,d-3.5);const c=i[0]==="w";r.rotation.y=c!==n?-Math.PI/2:Math.PI/2,Object.assign(r.userData,{piece:i,row:o,col:s}),this.pieces[`${o}-${s}`]=r,this.scene.add(r)}catch(r){console.error("Piece creation error:",i,r)}}}
958clearHL(){this.hlMeshes.forEach(a=>this.scene.remove(a)),this.hlMeshes=[],this.squareMeshes.forEach(a=>{a.material.color.setHex(a.userData.origColor),a.material.emissive&&a.material.emissive.setHex(0)})}setTheme(a){this.theme=a;const t={classic:{w:16116438,b:7233428,wR:.22,bR:.2,wM:.08,bM:.14},alien:{w:61047,b:12272895,wR:.12,bR:.12,wM:.25,bM:.25},christmas:{w:16775406,b:13378099,wR:.28,bR:.22,wM:.05,bM:.08},halloween:{w:16746513,b:2759230,wR:.25,bR:.18,wM:.1,bM:.2}},n=t[a]||t.classic;this.wMat.color.setHex(n.w),this.wMat.roughness=n.wR,this.wMat.metalness=n.wM,this.bMat.color.setHex(n.b),this.bMat.roughness=n.bR,this.bMat.metalness=n.bM,$&&this.sync($,A),N&&N.applyHighlights()}_makeAlienPiece(a){const t=a[0]==="w",n=a[1],o=(t?this.wMat:this.bMat).clone();o.emissive=new THREE.Color(t?13073:2228292),o.emissiveIntensity=.15;const s=new THREE.Group,i=new THREE.MeshStandardMaterial({color:4521864,emissive:65348,emissiveIntensity:.6,roughness:.3}),r=new THREE.MeshStandardMaterial({color:1118481,roughness:.2,metalness:.8}),d=new THREE.MeshStandardMaterial({color:13426107,roughness:.35,metalness:.1}),l=new THREE.MeshStandardMaterial({color:65484,emissive:65450,emissiveIntensity:1,roughness:.1,transparent:!0,opacity:.8}),c=new THREE.Mesh(new THREE.CylinderGeometry(.22,.28,.06,24),o);c.castShadow=!0,s.add(c);const p=new THREE.Mesh(new THREE.TorusGeometry(.25,.012,8,24),i);p.rotation.x=Math.PI/2,p.position.y=.03,s.add(p);const u=new THREE.Mesh(new THREE.TorusGeometry(.18,.008,8,20),i);u.rotation.x=Math.PI/2,u.position.y=.035,s.add(u);const g={parts:[]};if(n==="P"){const y=new THREE.Mesh(new THREE.SphereGeometry(.11,18,14),o);y.position.y=.14,y.scale.set(1.2,.5,1),y.castShadow=!0,s.add(y);for(let _=0;_<4;_++){const I=new THREE.Mesh(new THREE.ConeGeometry(.012,.06,5),o);I.position.set(.03-_*.02,.19,0),I.castShadow=!0,s.add(I)}const m=[];for(let _=-1;_<=1;_+=2)for(let I=0;I<4;I++){const E=(I-1.5)*.35,h=new THREE.Mesh(new THREE.CylinderGeometry(.008,.012,.12,6),o);h.position.set(-.04+I*.03,.12,_*.1),h.rotation.set(0,0,_*.8+E*.3),h.castShadow=!0,s.add(h),m.push({mesh:h,baseRot:_*.8+E*.3,side:_,idx:I});const S=new THREE.Mesh(new THREE.CylinderGeometry(.005,.008,.08,6),o);S.position.set(-.04+I*.03,.06,_*.18),S.rotation.set(0,0,_*1.2),S.castShadow=!0,s.add(S);const x=new THREE.Mesh(new THREE.ConeGeometry(.006,.02,4),i);x.position.set(-.04+I*.03,.02,_*.22),x.rotation.x=_*.5,s.add(x)}const v=new THREE.Mesh(new THREE.CylinderGeometry(.015,.008,.14,8),o);v.position.set(.14,.16,0),v.rotation.z=-.6,v.castShadow=!0,s.add(v);const T=new THREE.Mesh(new THREE.CylinderGeometry(.008,.004,.1,6),o);T.position.set(.2,.22,0),T.rotation.z=-1,T.castShadow=!0,s.add(T);const b=new THREE.Mesh(new THREE.ConeGeometry(.006,.04,4),i);b.position.set(.23,.28,0),b.rotation.z=-1.2,s.add(b);const R=new THREE.Mesh(new THREE.SphereGeometry(.03,10,8),i);R.position.set(-.12,.15,0),s.add(R);const M=new THREE.Mesh(new THREE.SphereGeometry(.015,8,6),r);M.position.set(-.14,.15,0),s.add(M);const k=Math.random()*100;s.userData._animFn=_=>{m.forEach(I=>{I.mesh.rotation.z=I.baseRot+Math.sin(_*4+I.idx*1.2+k)*.12}),v.rotation.z=-.6+Math.sin(_*2.5+k)*.15,T.rotation.z=-1+Math.sin(_*3+k)*.12,b.rotation.z=-1.2+Math.sin(_*3.5+k)*.1,R.material.emissiveIntensity=.4+Math.sin(_*5+k)*.3,p.rotation.z=_*.5}}else if(n==="R"){const y=new THREE.Mesh(new THREE.CylinderGeometry(.06,.14,.18,16),o);y.position.y=.12,y.castShadow=!0,s.add(y);const m=new THREE.Mesh(new THREE.CylinderGeometry(.32,.32,.04,32),o);m.position.y=.24,m.castShadow=!0,s.add(m);const v=new THREE.Mesh(new THREE.TorusGeometry(.32,.018,8,32),o);v.rotation.x=Math.PI/2,v.position.y=.24,v.castShadow=!0,s.add(v);const T=new THREE.Mesh(new THREE.CylinderGeometry(.2,.22,.04,24),o);T.position.y=.32,T.castShadow=!0,s.add(T);const b=new THREE.Mesh(new THREE.TorusGeometry(.21,.015,8,24),o);b.rotation.x=Math.PI/2,b.position.y=.32,b.castShadow=!0,s.add(b);const R=i.clone();R.transparent=!0,R.opacity=.6;const M=new THREE.Mesh(new THREE.SphereGeometry(.12,20,12,0,Math.PI*2,0,Math.PI/2),R);M.position.y=.34,M.castShadow=!0,s.add(M);const k=new THREE.Mesh(new THREE.CylinderGeometry(.005,.005,.12,6),o);k.position.y=.46,k.castShadow=!0,s.add(k);const _=new THREE.Mesh(new THREE.SphereGeometry(.018,10,8),l);_.position.y=.52,s.add(_);const I=new THREE.Mesh(new THREE.BoxGeometry(.08,.03,.04),r);I.position.set(-.3,.24,0),s.add(I);const E=[];for(let H=0;H<10;H++){const w=H/10*Math.PI*2,f=new THREE.Mesh(new THREE.SphereGeometry(.012,8,6),i.clone());
958f.position.set(Math.sin(w)*.3,.24,Math.cos(w)*.3),s.add(f),E.push(f)}for(let H=0;H<4;H++){const w=H/4*Math.PI*2+Math.PI/4,f=new THREE.Mesh(new THREE.ConeGeometry(.03,.04,8),o);f.position.set(Math.sin(w)*.18,.18,Math.cos(w)*.18),f.rotation.x=Math.PI,f.castShadow=!0,s.add(f)}const h=[];for(let H=0;H<12;H++){const w=new THREE.Mesh(new THREE.SphereGeometry(.012,8,6),l.clone());w.position.y=.2,s.add(w),h.push(w)}const S=l.clone();S.opacity=.15;const x=new THREE.Mesh(new THREE.ConeGeometry(.14,.16,16),S);x.position.y=.1,x.rotation.x=Math.PI,s.add(x);const C=Math.random()*100;s.userData._animFn=H=>{m.position.y=.24+Math.sin(H*2+C)*.015,T.position.y=.32+Math.sin(H*2+C)*.015,M.position.y=.34+Math.sin(H*2+C)*.015,h.forEach((w,f)=>{const L=H*2.5+f/12*Math.PI*2,P=.16+Math.sin(H*5+f*1.5)*.04;w.position.set(Math.sin(L)*P,.18+Math.sin(H*6+f)*.02,Math.cos(L)*P),w.scale.setScalar(.5+Math.sin(H*8+f*2)*.5),w.material.opacity=.4+Math.sin(H*7+f)*.4}),M.material.emissiveIntensity=.4+Math.sin(H*3+C)*.3,M.material.opacity=.4+Math.sin(H*2.5+C)*.2,_.scale.setScalar(.8+Math.sin(H*6)*.4),E.forEach((w,f)=>{w.material.emissiveIntensity=.3+Math.sin(H*4+f*.8)*.5}),p.rotation.z=H*.8,u.rotation.z=-H*1.2,x.material.opacity=.08+Math.sin(H*3)*.08}}else if(n==="N"){const y=new THREE.Mesh(new THREE.SphereGeometry(.18,24,20),o);y.position.y=.52,y.scale.set(.85,1.3,.8),y.castShadow=!0,s.add(y);for(let M=0;M<6;M++){const k=M/6*Math.PI*2,_=new THREE.Mesh(new THREE.CylinderGeometry(.008,.008,.28,6),o);_.position.set(Math.sin(k)*.12,.52,Math.cos(k)*.12),_.castShadow=!0,s.add(_)}for(let M=0;M<8;M++){const k=M/8*Math.PI*2,_=new THREE.Mesh(new THREE.SphereGeometry(.012,6,6),i.clone());_.position.set(Math.sin(k)*.13,.48+M%3*.06,Math.cos(k)*.13),s.add(_),g.parts.push({mesh:_,type:"pulse",idx:M})}const m=new THREE.Mesh(new THREE.CylinderGeometry(.1,.06,.12,14),o);m.position.y=.32,m.castShadow=!0,s.add(m),[-.1,.1].forEach(M=>{const k=new THREE.Mesh(new THREE.SphereGeometry(.06,12,10),o);k.position.set(-.08,.5,M),k.scale.set(.7,.8,1),k.castShadow=!0,s.add(k);const _=new THREE.Mesh(new THREE.SphereGeometry(.045,12,10),i);_.position.set(-.1,.5,M),s.add(_);const I=new THREE.Mesh(new THREE.SphereGeometry(.02,8,8),r);I.position.set(-.13,.5,M),s.add(I);const E=new THREE.Mesh(new THREE.TorusGeometry(.04,.006,6,12,Math.PI),o);E.position.set(-.1,.53,M),E.rotation.set(0,M>0?-.3:.3,0),s.add(E)});const v=new THREE.Mesh(new THREE.ConeGeometry(.025,.04,4),d);v.position.set(-.14,.34,0),v.rotation.z=Math.PI/2,s.add(v);const T=new THREE.Mesh(new THREE.ConeGeometry(.02,.03,4),d);T.position.set(-.14,.3,0),T.rotation.z=-Math.PI/2,s.add(T);const b=[];for(let M=0;M<8;M++){const k=M/8*Math.PI*2,_=[],I=5;for(let E=0;E<I;E++){const h=E/I,x=.032*(1-(E+1)/I*.85),C=.032*(1-h*.85),H=.07-E*.005,w=new THREE.Mesh(new THREE.CylinderGeometry(x,C,H,8),o),f=.08+h*.22,L=.28-h*.22;w.position.set(Math.sin(k)*f,L,Math.cos(k)*f);const P=.3+h*.9;if(w.rotation.set(Math.cos(k)*P,0,-Math.sin(k)*P),w.castShadow=!0,s.add(w),_.push({mesh:w,baseRotX:Math.cos(k)*P,baseRotZ:-Math.sin(k)*P,basePosY:L}),E<I-1){const Z=.007*(1-h*.6),fe=f+.015,ge=new THREE.Mesh(new THREE.TorusGeometry(Z,Z*.4,4,6),i);ge.position.set(Math.sin(k)*fe,L-.01,Math.cos(k)*fe),ge.rotation.x=Math.PI/2,s.add(ge)}}b.push({segs:_,angle:k,idx:M})}const R=Math.random()*100;s.userData._animFn=M=>{b.forEach(_=>{const I=M*1.6+_.idx*.8+R;_.segs.forEach((E,h)=>{const S=h/(_.segs.length-1),x=.04+S*.14,C=1+S*.5;E.mesh.rotation.x=E.baseRotX+Math.sin(I*C+h*.6)*x,E.mesh.rotation.z=E.baseRotZ+Math.cos(I*.8*C+h*.5)*x*.7,h>=3&&(E.mesh.position.y=E.basePosY+Math.sin(I+h)*.01)})});const k=1+Math.sin(M*2.5+R)*.04;y.scale.set(.85*k,1.3,.8*k),g.parts.forEach(_=>{_.type==="pulse"&&(_.mesh.material.emissiveIntensity=.3+Math.sin(M*4+_.idx*1.1+R)*.4,_.mesh.scale.setScalar(.8+Math.sin(M*5+_.idx)*.3))}),p.rotation.z=M*.5,u.rotation.z=-M*.8}}else if(n==="B"){[-.06,.06].forEach(w=>{const f=new THREE.Mesh(new THREE.CylinderGeometry(.025,.035,.14,8),o);f.position.set(.02,.1,w),f.rotation.z=.15,f.castShadow=!0,s.add(f);const L=new THREE.Mesh(new THREE.CylinderGeometry(.018,.025,.1,8),o);L.position.set(.02,.04,w*1.1),L.castShadow=!0,s.add(L)});const y=new THREE.Mesh(new THREE.CylinderGeometry(.06,.09,.3,12),o);y.position.y=.26,y.castShadow=!0,s.add(y);const m=[];for(let w=0;w<5;w++){const f=new THREE.Mesh(new THREE.TorusGeometry(.065+w*.002,.005,6,12,Math.PI),o);f.position.set(.02,.16+w*.05,0),f.rotation.set(0,Math.PI/2,0),f.castShadow=!0,s.add(f),m.push(f)}const v=new THREE.Mesh(new THREE.CylinderGeometry(.03,.05,.1,10),o);v.position.y=.44,v.castShadow=!0,s.add(v);const T=new THREE.Mesh(new THREE.SphereGeometry(.1,18,14),o);T.position.set(-.02,.54,0),T.scale.set(1,.7,.75),T.castShadow=!0,s.add(T);const b=new THREE.Mesh(new THREE.CylinderGeometry(.04,.08,.22,14),o);b.position.set(.1,.58,0),b.rotation.z=.6,b.castShadow=!0,s.add(b);const R=new THREE.Mesh(new THREE.SphereGeometry(.04,10,10),o);R.position.set(.2,.64,0),R.castShadow=!0,s.add(R);const M=new THREE.Mesh(new THREE.BoxGeometry(.04,.02,.06),r);M.position.set(-.1,.48,0),s.add(M);const k=new THREE.Mesh(new THREE.CylinderGeometry(.008,.012,.05,6),r);k.position.set(-.12,.47,0),k.rotation.z=Math.PI/2,s.add(k);const _=new THREE.Mesh(new THREE.ConeGeometry(.005,.015,4),d);_.position.set(-.1,.46,-.02),s.add(_);const I=_.clone();I.position.z=.02,s.add(I);const E=_.clone();E.position.set(-.1,.5,-.02),E.rotation.x=Math.PI,s.add(E);const h=_.clone();h.position.set(-.1,.5,.02),h.rotation.x=Math.PI,s.add(h);const S=[];for(let w=0;w<6;w++){const f=new THREE.Mesh(new THREE.ConeGeometry(.008,.05+w*.008,4),o);f.position.set(.04+w*.025,.38+w*.035,0),f.rotation.z=.6,f.castShadow=!0,s.add(f),S.push(f)}const x=new THREE.Mesh(new THREE.CylinderGeometry(.018,.008,.2,8),o);x.position.set(.15,.16,0),x.rotation.z=-.8,x.castShadow=!0,s.add(x);const C=new THREE.Mesh(new THREE.ConeGeometry(.012,.06,4),i);C.position.set(.24,.1,0),C.rotation.z=-1.2,s.add(C);const H=Math.random()*100;s.userData._animFn=w=>{m.forEach((L,P)=>{const Z=1+Math.sin(w*2.2+P*.4+H)*.06;L.scale.set(Z,1,Z)});const f=Math.sin(w*1.5+H);k.position.x=-.12-(f>.7?(f-.7)*.15:0),x.rotation.x=Math.sin(w*2+H)*.15,C.rotation.x=Math.sin(w*2.5+H)*.12,S.forEac
958h((L,P)=>{L.scale.y=1+Math.sin(w*3+P*.7+H)*.15}),p.rotation.z=w*.4}}else if(n==="Q"){const y=new THREE.Mesh(new THREE.CylinderGeometry(.1,.16,.36,16),o);y.position.y=.24,y.castShadow=!0,s.add(y),[-.06,0,.06].forEach(E=>{const h=new THREE.Mesh(new THREE.BoxGeometry(.08,.1,.04),o);h.position.set(-.08,.28,E),h.castShadow=!0,s.add(h)});for(let E=0;E<4;E++){const h=new THREE.Mesh(new THREE.TorusGeometry(.11+E*.005,.006,6,16,Math.PI),o);h.position.set(.02,.14+E*.06,0),h.rotation.y=Math.PI/2,s.add(h)}const m=new THREE.Mesh(new THREE.CylinderGeometry(.05,.08,.1,12),o);m.position.y=.46,m.castShadow=!0,s.add(m);const v=new THREE.Mesh(new THREE.SphereGeometry(.12,20,16),o);v.position.y=.56,v.scale.set(.9,.85,.8),v.castShadow=!0,s.add(v);const T=[];for(let E=0;E<7;E++){const h=(E-3)/3,S=.26-Math.abs(h)*.06,x=new THREE.Mesh(new THREE.ConeGeometry(.018,S,6),o);if(x.position.set(.08,.74+Math.abs(h)*.02,h*.05),x.rotation.z=.4+Math.abs(h)*.15,x.rotation.x=h*.3,x.castShadow=!0,s.add(x),T.push({mesh:x,baseH:S,idx:E}),E<6){const C=new THREE.Mesh(new THREE.PlaneGeometry(.015,S*.6),o.clone());C.position.set(.08,.72,(h+1/6)*.05),C.rotation.z=.5,C.material.transparent=!0,C.material.opacity=.4,C.material.side=THREE.DoubleSide,s.add(C)}}const b=[];
958[-.05,.05].forEach(E=>{const h=new THREE.Mesh(new THREE.SphereGeometry(.04,12,10),i.clone());h.position.set(-.09,.58,E),s.add(h),b.push(h);const S=new THREE.Mesh(new THREE.SphereGeometry(.018,8,6),r);S.position.set(-.12,.58,E),s.add(S)}),[-.08,.08].forEach(E=>{const h=new THREE.Mesh(new THREE.SphereGeometry(.02,8,6),i.clone());h.position.set(-.07,.54,E),s.add(h),b.push(h)});const R=[];[-.03,.03].forEach(E=>{const h=new THREE.Mesh(new THREE.CylinderGeometry(.006,.015,.08,6),o);h.position.set(-.14,.5,E),h.rotation.z=.6,h.rotation.x=E>0?-.2:.2,h.castShadow=!0,s.add(h),R.push({mesh:h,baseRotX:E>0?-.2:.2,side:E})});const M=o.clone();M.transparent=!0,M.opacity=.5;const k=new THREE.Mesh(new THREE.SphereGeometry(.1,14,12),M);k.position.set(.1,.18,0),k.scale.set(.8,1.2,1),s.add(k);const _=[];for(let E=0;E<4;E++){const h=new THREE.Mesh(new THREE.SphereGeometry(.015,8,8),l.clone());s.add(h),_.push(h)}const I=Math.random()*100;s.userData._animFn=E=>{T.forEach(h=>{h.mesh.scale.y=1+Math.sin(E*3+h.idx*.6+I)*.12}),b.forEach((h,S)=>{h.material.emissiveIntensity=.4+Math.sin(E*4+S*1.5+I)*.4}),R.forEach(h=>{h.mesh.rotation.x=h.baseRotX+Math.sin(E*3.5+I)*.15}),_.forEach((h,S)=>{const x=E*2+S/4*Math.PI*2;h.position.set(Math.sin(x)*.22,.5+Math.sin(E*3+S)*.06,Math.cos(x)*.22),h.scale.setScalar(.5+Math.sin(E*5+S)*.5),h.material.opacity=.4+Math.sin(E*6+S)*.4}),k.scale.y=1.2+Math.sin(E*1.5+I)*.06,p.rotation.z=E*.6}}else if(n==="K"){const y=new THREE.Mesh(new THREE.CylinderGeometry(.12,.18,.2,16),o);y.position.y=.13,y.castShadow=!0,s.add(y);const m=new THREE.Mesh(new THREE.CylinderGeometry(.08,.12,.22,14),o);m.position.y=.34,m.castShadow=!0,s.add(m);const v=new THREE.Mesh(new THREE.SphereGeometry(.22,24,20),o);v.position.y=.62,v.scale.set(.9,1.15,.85),v.castShadow=!0,s.add(v);for(let h=0;h<8;h++){const S=h/8*Math.PI*2,x=new THREE.Mesh(new THREE.TorusGeometry(.16,.008,6,20,Math.PI*.8),o);x.position.set(0,.58+h%3*.06,0),x.rotation.set(S,S*.5,0),x.castShadow=!0,s.add(x)}const T=new THREE.Mesh(new THREE.TorusGeometry(.18,.015,8,24),i);T.rotation.x=Math.PI/2,T.position.y=.86,s.add(T);const b=new THREE.Mesh(new THREE.TorusGeometry(.14,.01,6,20),i);b.rotation.x=Math.PI/2,b.position.y=.9,s.add(b);const R=[];for(let h=0;h<3;h++){const S=h/3*Math.PI*2,x=new THREE.Mesh(new THREE.ConeGeometry(.012,.14,6),i.clone());x.position.set(Math.sin(S)*.14,.92,Math.cos(S)*.14),x.castShadow=!0,s.add(x),R.push(x)}const M=[];[-.07,-.03,.03,.07].forEach(h=>{const S=new THREE.Mesh(new THREE.SphereGeometry(.03,10,8),i.clone());S.position.set(-.14,.6,h),s.add(S),M.push(S);const x=new THREE.Mesh(new THREE.SphereGeometry(.014,8,6),r);x.position.set(-.16,.6,h),s.add(x)});const k=[];for(let h=0;h<6;h++){const S=h/6*Math.PI*2,x=new THREE.Mesh(new THREE.CylinderGeometry(.008,.018,.18,6),o);x.position.set(Math.sin(S)*.1,.3,Math.cos(S)*.1),x.rotation.set(Math.cos(S)*.4,0,-Math.sin(S)*.4),x.castShadow=!0,s.add(x),k.push({mesh:x,baseRotX:Math.cos(S)*.4,baseRotZ:-Math.sin(S)*.4,angle:S})}const _=[];for(let h=0;h<8;h++){const S=new THREE.Mesh(new THREE.SphereGeometry(.008,6,6),l.clone());s.add(S),_.push(S)}const I=new THREE.Mesh(new THREE.TorusGeometry(.1,.012,8,20),i);I.rotation.x=Math.PI/2,I.position.y=.44,s.add(I);const E=Math.random()*100;s.userData._animFn=h=>{T.rotation.z=h*1.5,b.rotation.z=-h*2;const S=1+Math.sin(h*2+E)*.04;v.scale.set(.9*S,1.15*S,.85*S),R.forEach((x,C)=>{x.material.emissiveIntensity=.4+Math.sin(h*4+C*2+E)*.4,x.scale.y=1+Math.sin(h*3+C+E)*.15}),M.forEach((x,C)=>{x.material.emissiveIntensity=.3+Math.sin(h*5+C*.8+E)*.5}),k.forEach(x=>{x.mesh.rotation.x=x.baseRotX+Math.sin(h*1.8+x.angle+E)*.15,x.mesh.rotation.z=x.baseRotZ+Math.cos(h*1.5+x.angle+E)*.1}),_.forEach((x,C)=>{const H=h*2.2+C/8*Math.PI*2,w=.2+Math.sin(h*3+C)*.05,f=.62+Math.sin(h*2.5+C*1.3)*.1;x.position.set(Math.sin(H)*w,f,Math.cos(H)*w),x.scale.setScalar(.5+Math.sin(h*6+C)*.5),x.material.opacity=.3+Math.sin(h*4+C)*.4}),p.rotation.z=h*.5,u.rotation.z=-h*.7}}return s.scale.set(1.15,1.15,1.15),s}_makeChristmasPiece(a){const t=a[0]==="w",n=a[1],o=(t?this.wMat:this.bMat).clone(),s=new THREE.Group,i=new THREE.MeshStandardMaterial({color:16766720,roughness:.25,metalness:.6}),r=new THREE.MeshStandardMaterial({color:1732142,roughness:.35,metalness:.05}),d=new THREE.MeshStandardMaterial({color:16720418,roughness:.3,metalness:.2,emissive:3342336,emissiveIntensity:.2}),l=new THREE.MeshStandardMaterial({color:16777215,roughness:.5}),c=new THREE.Mesh(new THREE.CylinderGeometry(.24,.28,.06,24),o);if(c.castShadow=!0,s.add(c),n==="P"){const p=new THREE.Mesh(new THREE.BoxGeometry(.26,.26,.26),o);p.position.y=.19,p.castShadow=!0,s.add(p);const u=new THREE.Mesh(new THREE.BoxGeometry(.28,.04,.04),i);u.position.y=.19,u.castShadow=!0,s.add(u);const g=new THREE.Mesh(new THREE.BoxGeometry(.04,.04,.28),i);g.position.y=.19,g.castShadow=!0,s.add(g);const y=new THREE.Mesh(new THREE.BoxGeometry(.04,.28,.04),i);
958y.position.y=.19,y.castShadow=!0,s.add(y);const m=new THREE.Mesh(new THREE.SphereGeometry(.05,10,8),i);m.position.set(.05,.36,0),m.scale.set(1.4,.6,.8),s.add(m);const v=m.clone();v.position.set(-.05,.36,0),s.add(v);const T=new THREE.Mesh(new THREE.SphereGeometry(.03,8,8),i);T.position.y=.36,s.add(T);const b=Math.random()*100;s.userData._animFn=R=>{m.scale.x=1.4+Math.sin(R*3+b)*.15,v.scale.x=1.4+Math.sin(R*3+b+1)*.15,p.rotation.y=Math.sin(R*1.5+b)*.03}}else if(n==="R"){const p=new THREE.Mesh(new THREE.BoxGeometry(.28,.52,.28),o);p.position.y=.32,p.castShadow=!0,s.add(p);const u=new THREE.Mesh(new THREE.BoxGeometry(.32,.06,.32),o);u.position.y=.61,u.castShadow=!0,s.add(u);for(let m=0;m<4;m++){const v=new THREE.Mesh(new THREE.BoxGeometry(.3,.005,.005),new THREE.MeshStandardMaterial({color:3355443,roughness:.8}));v.position.set(0,.12+m*.12,.142),v.castShadow=!0,s.add(v);const T=v.clone();T.position.z=-.142,s.add(T)}const g=new THREE.Mesh(new THREE.BoxGeometry(.34,.04,.34),l);g.position.y=.66,g.castShadow=!0,s.add(g);const y=Math.random()*100;s.userData._animFn=m=>{g.position.y=.66+Math.sin(m*2+y)*.005}}else if(n==="N"){const p=new THREE.Mesh(new THREE.CylinderGeometry(.06,.1,.28,12),o);p.position.y=.2,p.castShadow=!0,s.add(p);const u=new THREE.Mesh(new THREE.SphereGeometry(.12,16,14),o);u.position.set(-.04,.44,0),u.scale.set(1.1,.9,.85),u.castShadow=!0,s.add(u),[-.08,.08].forEach(v=>{const T=new THREE.Mesh(new THREE.CylinderGeometry(.012,.012,.18,6),o);T.position.set(.02,.58,v),T.rotation.z=v>0?-.3:.3,T.castShadow=!0,s.add(T);const b=new THREE.Mesh(new THREE.CylinderGeometry(.008,.008,.1,6),o);b.position.set(v>0?.08:-.08,.66,v*1.2),b.rotation.z=v>0?-.8:.8,b.castShadow=!0,s.add(b);const R=new THREE.Mesh(new THREE.CylinderGeometry(.008,.008,.08,6),o);R.position.set(v>0?.04:-.04,.62,v*1.1),R.rotation.z=v>0?.4:-.4,R.castShadow=!0,s.add(R)});const g=new THREE.Mesh(new THREE.SphereGeometry(.035,10,10),d);g.position.set(-.15,.42,0),s.add(g);const y=new THREE.MeshStandardMaterial({color:1118481,roughness:.3});[-.04,.04].forEach(v=>{const T=new THREE.Mesh(new THREE.SphereGeometry(.02,8,8),y);T.position.set(-.12,.47,v),s.add(T)});const m=Math.random()*100;
958s.userData._animFn=v=>{g.material.emissiveIntensity=.1+Math.sin(v*4+m)*.3,g.scale.setScalar(.8+Math.sin(v*5+m)*.3),u.rotation.x=Math.sin(v*2+m)*.04,u.position.y=.44+Math.sin(v*2.5+m)*.008}}else if(n==="B"){const p=new THREE.Mesh(new THREE.CylinderGeometry(.04,.06,.12,8),o);p.position.y=.12,p.castShadow=!0,s.add(p),[[.28,.24,.22],[.22,.2,.36],[.16,.16,.5]].forEach(([v,T,b])=>{const R=new THREE.Mesh(new THREE.ConeGeometry(v,T,12),r);R.position.y=b,R.castShadow=!0,s.add(R)});const g=new THREE.Mesh(new THREE.OctahedronGeometry(.04,0),i);g.position.y=.72,g.rotation.z=Math.PI/4,g.castShadow=!0,s.add(g);const y=[16720418,2245887,16768256];for(let v=0;v<5;v++){const T=v/5*Math.PI*2,b=v<2?0:v<4?1:2,R=[.18,.14,.1][b],M=[.22,.36,.5][b],k=new THREE.Mesh(new THREE.SphereGeometry(.02,8,8),new THREE.MeshStandardMaterial({color:y[v%3],roughness:.2,metalness:.4}));k.position.set(Math.sin(T)*R,M,Math.cos(T)*R),s.add(k)}const m=Math.random()*100;s.userData._animFn=v=>{g.rotation.y=v*2,g.scale.setScalar(.8+Math.sin(v*4+m)*.3)}}else if(n==="Q"){const p=new THREE.Mesh(new THREE.ConeGeometry(.14,.4,12),o);p.position.y=.26,p.castShadow=!0,s.add(p);const u=new THREE.Mesh(new THREE.SphereGeometry(.1,16,14),o);u.position.y=.54,u.castShadow=!0,s.add(u),[-.01,.01].forEach(v=>{const T=new THREE.Mesh(new THREE.SphereGeometry(.12,12,10),l);T.position.set(.1,.38,v>0?.12:-.12),T.scale.set(.3,.8,1),T.castShadow=!0,s.add(T)});const g=new THREE.Mesh(new THREE.TorusGeometry(.08,.012,8,20),i);g.rotation.x=Math.PI/2,g.position.y=.68,s.add(g);const y=new THREE.MeshStandardMaterial({color:2245802,roughness:.3});[-.03,.03].forEach(v=>{const T=new THREE.Mesh(new THREE.SphereGeometry(.015,8,8),y);T.position.set(-.08,.56,v),s.add(T)});const m=Math.random()*100;
958s.userData._animFn=v=>{g.rotation.z=Math.sin(v*2+m)*.1,g.position.y=.68+Math.sin(v*3+m)*.01,p.position.y=.26+Math.sin(v*2+m)*.008,u.position.y=.54+Math.sin(v*2+m)*.008}}else if(n==="K"){const p=new THREE.Mesh(new THREE.SphereGeometry(.18,18,16),o);p.position.y=.26,p.scale.set(1,1.2,.9),p.castShadow=!0,s.add(p);const u=new THREE.Mesh(new THREE.TorusGeometry(.16,.02,8,20),new THREE.MeshStandardMaterial({color:1118481,roughness:.5}));u.rotation.x=Math.PI/2,u.position.y=.22,s.add(u);const g=new THREE.Mesh(new THREE.BoxGeometry(.06,.05,.04),i);g.position.set(-.16,.22,0),s.add(g);const y=new THREE.Mesh(new THREE.SphereGeometry(.11,16,14),new THREE.MeshStandardMaterial({color:16768181,roughness:.5}));y.position.y=.48,y.castShadow=!0,s.add(y);const m=new THREE.Mesh(new THREE.ConeGeometry(.1,.22,12),d);m.position.set(.03,.64,0),m.rotation.z=.2,m.castShadow=!0,s.add(m);const v=new THREE.Mesh(new THREE.TorusGeometry(.11,.02,8,20),l);v.rotation.x=Math.PI/2,v.position.y=.56,s.add(v);const T=new THREE.Mesh(new THREE.SphereGeometry(.03,10,8),l);T.position.set(.08,.76,0),s.add(T);const b=new THREE.Mesh(new THREE.SphereGeometry(.1,14,12),l);b.position.set(-.06,.4,0),b.scale.set(.8,1.2,.9),b.castShadow=!0,s.add(b);const R=new THREE.MeshStandardMaterial({color:2237064,roughness:.3});[-.03,.03].forEach(_=>{const I=new THREE.Mesh(new THREE.SphereGeometry(.015,8,8),R);I.position.set(-.09,.5,_),s.add(I)});const M=new THREE.Mesh(new THREE.SphereGeometry(.025,8,8),d);M.position.set(-.11,.46,0),s.add(M);const k=Math.random()*100;s.userData._animFn=_=>{T.position.y=.76+Math.sin(_*3+k)*.015,T.position.x=.08+Math.sin(_*2+k)*.01,p.scale.x=1+Math.sin(_*2.5+k)*.02,b.scale.y=1.2+Math.sin(_*2+k)*.03,M.material.emissiveIntensity=.1+Math.sin(_*4+k)*.15}}return s.scale.set(1.15,1.15,1.15),s}_makeHalloweenPiece(a){const t=a[0]==="w",n=a[1],o=(t?this.wMat:this.bMat).clone(),s=new THREE.Group,i=new THREE.MeshStandardMaterial({color:15260872,roughness:.45,metalness:.05}),r=new THREE.MeshStandardMaterial({color:8978346,emissive:2293572,emissiveIntensity:.4,roughness:.3}),d=new THREE.MeshStandardMaterial({color:1710638,roughness:.3,metalness:.15}),l=new THREE.MeshStandardMaterial({color:11141120,roughness:.3,metalness:.2,emissive:3342336,emissiveIntensity:.2}),c=new THREE.MeshStandardMaterial({color:6044190,roughness:.6,metalness:0}),p=new THREE.MeshStandardMaterial({color:8423568,roughness:.65,metalness:.05}),u=new THREE.MeshStandardMaterial({color:6963248,roughness:.55,metalness:.05}),g=new THREE.MeshStandardMaterial({color:5914664,roughness:.75,metalness:0}),y=new THREE.MeshStandardMaterial({color:1714718,roughness:.4,metalness:.1}),m=new THREE.MeshStandardMaterial({color:2755098,roughness:.35,metalness:.15}),v=new THREE.Mesh(new THREE.CylinderGeometry(.22,.28,.06,8),o);v.castShadow=!0,s.add(v);const T=new THREE.Mesh(new THREE.TorusGeometry(.25,.01,6,8),o);if(T.rotation.x=Math.PI/2,T.position.y=.03,s.add(T),n==="P"){const b=new THREE.Mesh(new THREE.BoxGeometry(.06,.38,.22),p);b.position.y=.25,b.castShadow=!0,s.add(b);const R=new THREE.Mesh(new THREE.CylinderGeometry(.11,.11,.06,16,1,!1,0,Math.PI),p);R.position.y=.44,R.rotation.x=-Math.PI/2,R.castShadow=!0,s.add(R);const M=new THREE.Mesh(new THREE.BoxGeometry(.008,.14,.03),o);M.position.set(-.035,.3,0),s.add(M);const k=new THREE.Mesh(new THREE.BoxGeometry(.008,.03,.1),o);k.position.set(-.035,.34,0),s.add(k);const _=new THREE.Mesh(new THREE.BoxGeometry(.07,.1,.005),d);_.position.set(0,.2,.06),_.rotation.z=.3,s.add(_);const I=new THREE.Mesh(new THREE.BoxGeometry(.07,.07,.005),d);I.position.set(0,.15,-.04),I.rotation.z=-.4,s.add(I);const E=new THREE.Mesh(new THREE.SphereGeometry(.14,10,8),c);E.position.y=.04,E.scale.set(.8,.3,1),E.castShadow=!0,s.add(E);const h=[];for(let x=0;x<3;x++){const C=new THREE.Mesh(new THREE.SphereGeometry(.015,6,6),r.clone());C.material.transparent=!0,C.material.opacity=.4,s.add(C),h.push(C)}const S=Math.random()*100;s.userData._animFn=x=>{h.forEach((C,H)=>{const w=x*1.5+H*2.1+S;C.position.set(Math.sin(w)*.12,.35+Math.sin(x*2+H)*.08,Math.cos(w)*.12),C.material.opacity=.2+Math.sin(x*3+H)*.2,C.scale.setScalar(.5+Math.sin(x*4+H)*.4)}),T.rotation.z=x*.3}}else if(n==="R"){const b=new THREE.Mesh(new THREE.CylinderGeometry(.14,.18,.48,8),u);b.position.y=.3,b.castShadow=!0,s.add(b);for(let S=0;S<6;S++){const x=S/6*Math.PI*2,C=new THREE.Mesh(new THREE.BoxGeometry(.05,.08,.05),u);C.position.set(Math.sin(x)*.14,.58,Math.cos(x)*.14),C.castShadow=!0,s.add(C)}const R=[];
958[0,Math.PI/2,Math.PI,Math.PI*1.5].forEach((S,x)=>{const C=new THREE.Mesh(new THREE.BoxGeometry(.04,.08,.03),r.clone());C.position.set(Math.sin(S)*.16,.36-x%2*.1,Math.cos(S)*.16),s.add(C),R.push(C)});const M=new THREE.Mesh(new THREE.ConeGeometry(.18,.2,8),o);M.position.y=.64,M.castShadow=!0,s.add(M);const k=new THREE.Mesh(new THREE.CylinderGeometry(.004,.004,.1,4),d);k.position.y=.78,s.add(k);const _=new THREE.Mesh(new THREE.SphereGeometry(.015,6,6),d);_.position.set(-.18,.46,.05),s.add(_);const I=new THREE.Mesh(new THREE.PlaneGeometry(.04,.015),d);I.position.set(-.2,.46,.05),s.add(I);const E=new THREE.Mesh(new THREE.PlaneGeometry(.04,.015),d);E.position.set(-.16,.46,.05),s.add(E);const h=Math.random()*100;s.userData._animFn=S=>{R.forEach((x,C)=>{x.material.emissiveIntensity=.2+Math.sin(S*3+C*1.7+h)*.3}),I.rotation.z=.3+Math.sin(S*8+h)*.4,E.rotation.z=-.3-Math.sin(S*8+h)*.4,k.rotation.y=S*2,T.rotation.z=S*.2}}else if(n==="N"){const b=new THREE.Mesh(new THREE.CylinderGeometry(.08,.12,.28,12),g);b.position.y=.2,b.castShadow=!0,s.add(b);const R=new THREE.Mesh(new THREE.SphereGeometry(.12,14,10),g);R.position.y=.36,R.scale.set(1.2,.5,.9),R.castShadow=!0,s.add(R);const M=new THREE.Mesh(new THREE.CylinderGeometry(.05,.07,.08,10),g);M.position.y=.42,M.castShadow=!0,s.add(M);const k=new THREE.Mesh(new THREE.SphereGeometry(.1,16,14),g);k.position.y=.52,k.scale.set(.9,1,.85),k.castShadow=!0,s.add(k);const _=new THREE.Mesh(new THREE.CylinderGeometry(.03,.05,.1,10),g);_.position.set(-.1,.48,0),_.rotation.z=Math.PI/2,_.castShadow=!0,s.add(_);const I=new THREE.Mesh(new THREE.SphereGeometry(.025,8,8),d);I.position.set(-.15,.49,0),s.add(I);const E=new THREE.Mesh(new THREE.CylinderGeometry(.025,.04,.08,8),g);E.position.set(-.1,.44,0),E.rotation.z=Math.PI/2,E.castShadow=!0,s.add(E),[-.02,.02].forEach(w=>{const f=new THREE.Mesh(new THREE.ConeGeometry(.006,.03,4),i);f.position.set(-.12,.42,w),f.rotation.x=Math.PI,s.add(f);const L=new THREE.Mesh(new THREE.ConeGeometry(.006,.025,4),i);L.position.set(-.13,.46,w),s.add(L)});const h=new THREE.MeshStandardMaterial({color:16763904,emissive:16746496,emissiveIntensity:.8,roughness:.2}),S=[];[-.04,.04].forEach(w=>{const f=new THREE.Mesh(new THREE.SphereGeometry(.02,10,8),h.clone());f.position.set(-.08,.54,w),s.add(f),S.push(f)});const x=[];[-.06,.06].forEach(w=>{const f=new THREE.Mesh(new THREE.ConeGeometry(.025,.08,4),g);f.position.set(.02,.62,w),f.rotation.z=w>0?-.2:.2,f.castShadow=!0,s.add(f),x.push({mesh:f,baseRotZ:w>0?-.2:.2})});for(let w=0;w<6;w++){const f=w/6*Math.PI*2,L=new THREE.Mesh(new THREE.ConeGeometry(.008,.05,4),g);L.position.set(Math.sin(f)*.1,.38,Math.cos(f)*.1),L.rotation.set(Math.cos(f)*.4,0,Math.sin(f)*.4),s.add(L)}[-.1,.1].forEach(w=>{const f=new THREE.Mesh(new THREE.CylinderGeometry(.018,.025,.14,6),g);f.position.set(-.02,.28,w),f.rotation.z=w>0?.5:-.5,f.castShadow=!0,s.add(f);for(let L=-1;L<=1;L++){const P=new THREE.Mesh(new THREE.ConeGeometry(.004,.025,4),i);P.position.set(-.06,.2,w+L*.012),P.rotation.z=w>0?.8:-.8,s.add(P)}});const C=new THREE.Mesh(new THREE.TorusGeometry(.08,.012,6,16),o);C.rotation.x=Math.PI/2,C.position.y=.4,s.add(C);const H=Math.random()*100;s.userData._animFn=w=>{const f=1+Math.sin(w*2.5+H)*.03;
958b.scale.set(f,1,f),R.scale.set(1.2*f,.5,.9*f),S.forEach((L,P)=>{L.material.emissiveIntensity=.5+Math.sin(w*4+P+H)*.4}),x.forEach((L,P)=>{L.mesh.rotation.z=L.baseRotZ+Math.sin(w*5+P*3+H)*.08}),E.rotation.x=Math.sin(w*3+H)*.05,T.rotation.z=w*.3}}else if(n==="B"){const b=new THREE.Mesh(new THREE.ConeGeometry(.16,.36,12),y);b.position.y=.24,b.castShadow=!0,s.add(b);const R=new THREE.Mesh(new THREE.TorusGeometry(.16,.012,6,16),y);R.rotation.x=Math.PI/2,R.position.y=.08,s.add(R);const M=new THREE.Mesh(new THREE.CylinderGeometry(.05,.08,.14,10),y);M.position.y=.4,M.castShadow=!0,s.add(M);const k=new THREE.Mesh(new THREE.SphereGeometry(.08,14,12),new THREE.MeshStandardMaterial({color:9485168,roughness:.5}));k.position.y=.52,k.castShadow=!0,s.add(k);const _=new THREE.Mesh(new THREE.CylinderGeometry(.14,.14,.015,16),d);_.position.y=.58,_.castShadow=!0,s.add(_);const I=new THREE.Mesh(new THREE.ConeGeometry(.08,.24,10),d);I.position.y=.72,I.rotation.z=.08,I.castShadow=!0,s.add(I);const E=new THREE.Mesh(new THREE.BoxGeometry(.04,.03,.02),o);E.position.set(-.08,.62,0),s.add(E);const h=new THREE.Mesh(new THREE.ConeGeometry(.012,.04,4),new THREE.MeshStandardMaterial({color:8432224,roughness:.5}));h.position.set(-.08,.5,0),h.rotation.z=Math.PI/2,s.add(h);const S=[];[-.025,.025].forEach(w=>{const f=new THREE.Mesh(new THREE.SphereGeometry(.012,8,6),r.clone());f.position.set(-.06,.53,w),s.add(f),S.push(f)});const x=new THREE.Mesh(new THREE.CylinderGeometry(.008,.008,.4,6),c);x.position.set(.1,.26,.06),x.rotation.z=.15,x.castShadow=!0,s.add(x);const C=new THREE.Mesh(new THREE.ConeGeometry(.04,.1,6),c);C.position.set(.12,.04,.06),C.castShadow=!0,s.add(C);const H=Math.random()*100;s.userData._animFn=w=>{I.rotation.z=.08+Math.sin(w*2+H)*.06,S.forEach((f,L)=>{f.material.emissiveIntensity=.2+Math.sin(w*5+L*2+H)*.3}),x.rotation.x=Math.sin(w*1.5+H)*.05,b.rotation.y=Math.sin(w*1.2+H)*.03,T.rotation.z=w*.25}}else if(n==="Q"){const b=new THREE.Mesh(new THREE.ConeGeometry(.16,.38,14),m);b.position.y=.25,b.castShadow=!0,s.add(b);const R=new THREE.Mesh(new THREE.CylinderGeometry(.055,.08,.18,12),m);R.position.y=.42,R.castShadow=!0,s.add(R);const M=[];[-.01,.01].forEach(C=>{const H=new THREE.Mesh(new THREE.PlaneGeometry(.24,.36),l.clone());H.position.set(.06,.3,C>0?.08:-.08),H.rotation.y=C>0?-.4:.4,H.material.side=THREE.DoubleSide,H.castShadow=!0,s.add(H),M.push({mesh:H,baseRotY:C>0?-.4:.4})}),[-.06,.06].forEach(C=>{const H=new THREE.Mesh(new THREE.BoxGeometry(.04,.12,.01),l);H.position.set(.04,.48,C),H.rotation.y=C>0?-.3:.3,s.add(H)});const k=new THREE.Mesh(new THREE.SphereGeometry(.08,14,12),new THREE.MeshStandardMaterial({color:15260896,roughness:.4}));k.position.y=.56,k.castShadow=!0,s.add(k);const _=new THREE.MeshStandardMaterial({color:16711680,emissive:11141120,emissiveIntensity:.6,roughness:.2}),I=[];[-.03,.03].forEach(C=>{const H=new THREE.Mesh(new THREE.SphereGeometry(.015,8,8),_.clone());H.position.set(-.06,.58,C),s.add(H),I.push(H)}),[-.015,.015].forEach(C=>{const H=new THREE.Mesh(new THREE.ConeGeometry(.004,.025,4),i);H.position.set(-.06,.5,C),s.add(H)});const E=new THREE.Mesh(new THREE.SphereGeometry(.09,12,10),d);E.position.set(.02,.58,0),E.scale.set(.9,1,.9),E.castShadow=!0,s.add(E);const h=new THREE.Mesh(new THREE.TorusGeometry(.06,.008,6,12,Math.PI),o);h.position.set(-.02,.64,0),h.rotation.x=.3,s.add(h);const S=new THREE.Mesh(new THREE.OctahedronGeometry(.015,0),r);S.position.set(-.04,.66,0),s.add(S);const x=Math.random()*100;s.userData._animFn=C=>{M.forEach((H,w)=>{H.mesh.rotation.y=H.baseRotY+Math.sin(C*2+w*Math.PI+x)*.08}),I.forEach((H,w)=>{H.material.emissiveIntensity=.3+Math.sin(C*4+w+x)*.4}),S.rotation.y=C*3,S.scale.setScalar(.8+Math.sin(C*5+x)*.3),b.rotation.y=Math.sin(C*1.3+x)*.02,T.rotation.z=C*.3}}else if(n==="K"){const b=new THREE.Mesh(new THREE.ConeGeometry(.18,.52,14),d);b.position.y=.32,b.castShadow=!0,s.add(b);const R=[];for(let w=0;w<4;w++){const f=w/4*Math.PI*2,L=new THREE.Mesh(new THREE.BoxGeometry(.01,.4,.04),d);L.position.set(Math.sin(f)*.1,.28,Math.cos(f)*.1),L.rotation.y=f,s.add(L),R.push(L)}const M=new THREE.Mesh(new THREE.SphereGeometry(.12,16,12,0,Math.PI*2,0,Math.PI*.65),d);
958M.position.y=.62,M.castShadow=!0,s.add(M);const k=new THREE.Mesh(new THREE.SphereGeometry(.08,12,10),new THREE.MeshStandardMaterial({color:328976,roughness:.9}));k.position.set(-.03,.6,0),k.scale.set(.7,.8,.7),s.add(k);const _=new THREE.MeshStandardMaterial({color:16729088,emissive:16720384,emissiveIntensity:.9,roughness:.1}),I=[];[-.025,.025].forEach(w=>{const f=new THREE.Mesh(new THREE.SphereGeometry(.012,8,8),_.clone());f.position.set(-.08,.61,w),s.add(f),I.push(f)});const E=new THREE.Mesh(new THREE.CylinderGeometry(.01,.01,.7,6),c);E.position.set(.14,.38,.08),E.rotation.z=.12,E.castShadow=!0,s.add(E);const h=new THREE.Mesh(new THREE.TorusGeometry(.12,.008,4,12,Math.PI*.6),new THREE.MeshStandardMaterial({color:12303308,roughness:.15,metalness:.8}));h.position.set(.1,.72,.08),h.rotation.set(Math.PI/2,.3,.5),h.castShadow=!0,s.add(h);const S=new THREE.Mesh(new THREE.TorusGeometry(.11,.003,3,10,Math.PI*.55),new THREE.MeshStandardMaterial({color:15658751,roughness:.1,metalness:.9,emissive:4474111,emissiveIntensity:.2}));S.position.set(.1,.72,.08),S.rotation.set(Math.PI/2,.3,.5),s.add(S);const x=new THREE.Mesh(new THREE.SphereGeometry(.02,8,8),i);x.position.set(.08,.52,.06),s.add(x),[-.008,0,.008].forEach(w=>{const f=new THREE.Mesh(new THREE.CylinderGeometry(.003,.003,.025,4),i);f.position.set(.06,.5,.06+w),f.rotation.z=.5,s.add(f)});const C=[];for(let w=0;w<4;w++){const f=new THREE.Mesh(new THREE.SphereGeometry(.01,6,6),r.clone());f.material.transparent=!0,f.material.opacity=.5,s.add(f),C.push(f)}const H=Math.random()*100;s.userData._animFn=w=>{R.forEach((f,L)=>{f.rotation.z=Math.sin(w*1.5+L*1.5+H)*.05}),b.rotation.y=Math.sin(w*1.2+H)*.03,I.forEach((f,L)=>{f.material.emissiveIntensity=.5+Math.sin(w*6+L*2+H)*.4}),S.material.emissiveIntensity=.1+Math.sin(w*3+H)*.2,C.forEach((f,L)=>{const P=w*1.8+L/4*Math.PI*2;f.position.set(Math.sin(P)*.2,.4+Math.sin(w*2.5+L)*.12,Math.cos(P)*.2),f.material.opacity=.2+Math.sin(w*4+L)*.2,f.scale.setScalar(.4+Math.sin(w*5+L)*.4)}),T.rotation.z=w*.25}}return s.scale.set(1.15,1.15,1.15),s}hlSquare(a,t,n,o){const s=o==="black",i=s?7-a:a,r=s?7-t:t,d=this.squareMeshes.find(l=>l.userData.row===i&&l.userData.col===r);if(d)switch(n){case"selected":d.material.color.setHex(16698117),d.material.emissive=new THREE.Color(3352576);break;case"legal":{const l=new THREE.Mesh(new THREE.CylinderGeometry(.1,.1,.08,16),new THREE.MeshStandardMaterial({color:16757760,emissive:6703104,roughness:.3}));l.position.set(r-3.5,.05,i-3.5),this.scene.add(l),this.hlMeshes.push(l);break}case"capture":{const l=new THREE.Mesh(new THREE.TorusGeometry(.38,.04,8,24),new THREE.MeshStandardMaterial({color:16746496,emissive:6693376,roughness:.3}));l.rotation.x=-Math.PI/2,l.position.set(r-3.5,.06,i-3.5),this.scene.add(l),this.hlMeshes.push(l);break}case"check":d.material.color.setHex(13378082),d.material.emissive=new THREE.Color(4456448);break;case"last-from":d.material.color.setHex(15257696);break;case"last-to":d.material.color.setHex(13940800);break}}applyHighlights(){if(this.clearHL(),U&&this.hlSquare(U.row,U.col,"selected",A),se.forEach(a=>{const t=$?.board[a.row][a.col];this.hlSquare(a.row,a.col,t?"capture":"legal",A)}),$){const a=$.turn;if(ve($,a))for(let t=0;t<8;t++)for(let n=0;n<8;n++){const o=$.board[t][n];o&&o[1]==="K"&&(a==="white"&&o[0]==="w"||a==="black"&&o[0]==="b")&&this.hlSquare(t,n,"check",A)}}Be&&this.hlSquare(Be.row,Be.col,"last-from",A),Pe&&this.hlSquare(Pe.row,Pe.col,"last-to",A)}animateMove3D(a,t,n,o){const s=A==="black",i=`${a.row}-${a.col}`;let r=this.pieces[i];if(!r){o();return}const d=s?7-t.row:t.row,c=(s?7-t.col:t.col)-3.5,p=d-3.5,u=r.position.clone(),g=performance.now(),v=350+Math.sqrt((c-u.x)**2+(p-u.z)**2)*60,T=n?n[1]:"P",b=this.theme||"classic",R=r.rotation.y,M=r.rotation.x,k=r.rotation.z;r.userData._isMoving=!0;const _=[];if(b==="alien"&&(T==="R"||T==="Q"||T==="K")||b==="halloween"&&(T==="B"||T==="K")){const h=new THREE.MeshStandardMaterial({color:b==="alien"?65484:8978346,emissive:b==="alien"?65416:4521830,emissiveIntensity:.8,roughness:.2,transparent:!0,opacity:.6});for(let S=0;S<5;S++){const x=new THREE.Mesh(new THREE.SphereGeometry(.012,6,6),h.clone());this.scene.add(x),_.push(x)}}const E=()=>{const h=Math.min(1,(performance.now()-g)/v),S=h<.5?4*h*h*h:1-Math.pow(-2*h+2,3)/2,x=u.x+(c-u.x)*S,C=u.z+(p-u.z)*S,H=Math.sin(h*Math.PI),w=performance.now()*.001;let f=0;if(b==="alien")switch(T){case"R":f=H*.55,r.rotation.z=k+Math.sin(h*Math.PI*2)*.04;break;case"P":f=Math.abs(Math.sin(h*Math.PI*7))*.025,r.rotation.z=k+Math.sin(h*Math.PI*12)*.08;break;case"N":f=H*.2+Math.sin(h*Math.PI*3)*.05;break;case"B":f=H*.06,r.rotation.x=M+Math.sin(h*Math.PI)*-.2;break;case"Q":f=H*.3;break;case"K":f=H*.35;break;default:f=H*.15}else if(b==="halloween")switch(T){case"P":h<.25?f=-h*4*.25:h<.75?f=-.25:f=-(1-h)*4*.25;break;case"R":f=H*.04,r.rotation.z=k+Math.sin(w*40)*.008;break;case"N":f=Math.abs(Math.sin(h*Math.PI*4))*.18,r.rotation.x=M+Math.sin(h*Math.PI*4)*-.12;break;case"B":f=H*.55,r.rotation.z=k+Math.sin(h*Math.PI)*.1;break;case"Q":f=H*.28;break;case"K":f=.04+H*.08;break;default:f=H*.15}else if(b==="christmas")switch(T){case"P":f=H*.1,r.rotation.z=k+Math.sin(h*Math.PI*6)*.05;break;case"R":f=H*.05;break;case"N":f=Math.abs(Math.sin(h*Math.PI*5))*.12;break;case"B":f=H*.15;break;case"Q":f=H*.4;break;case"K":f=H*.08,r.rotation.z=k+Math.sin(h*Math.PI*8)*.06;break;default:f=H*.12}else f=H*.12;r.position.set(x,f,C),r.rotation.y=R,_.length>0&&_.forEach((L,P)=>{const Z=(P+1)*.06,fe=Math.max(0,h-Z),ge=u.x+(c-u.x)*fe,ea=u.z+(p-u.z)*fe;L.position.set(ge,f*.5+Math.sin(w*8+P)*.02,ea),L.material.opacity=Math.max(0,.5-h*.6),L.scale.setScalar(.3+(1-h)*.7)}),h<1?requestAnimationFrame(E):(r.position.set(c,0,p),r.rotation.y=R,r.rotation.x=M,r.rotation.z=k,r.scale.set(1.15,1.15,1.15),delete r.userData._isMoving,_.forEach(L=>this.scene.remove(L)),o())};requestAnimationFrame(E)}_applyCameraOrbit(){const a=this._orbitRadius,t=this._orbitPhi,n=this._orbitTheta;this.camera.position.set(this._orbitTarget.x+a*Math.sin(t)*Math.sin(n),this._orbitTarget.y+a*Math.cos(t),this._orbitTarget.z+a*Math.sin(t)*Math.cos(n)),this.camera.lookAt(this._orbitTarget)}_events(){const a=this.renderer.domElement;let t={x:0,y:0};const n=l=>{const c=a.getBoundingClientRect(),p=l.touches?l.touches[0]:l;return{x:(p.clientX-c.left)/c.width*2-1,y:-((p.clientY-c.top)/c.height)*2+1}},o=l=>{const c=l.touches?l.touches[0]:l;return{cx:c.clientX,cy:c.clientY}},s=l=>{const{cx:c,cy:p}=o(l);if(t={x:c,y:p},l.button===2){l.preventDefault(),this._orbiting=!0,a.style.cursor="grabbing";return}const u=n(l);if(this.mouse.set(u.x,u.y),this.raycaster.setFromCamera(this.mouse,this.camera),$&&!$.gameOver&&!Xe&&$.turn===A&&!(j&&ae)){const y=Object.values(this.pieces),m=this.raycaster.intersectObjects(y,!0);if(m.length>0){let v=m[0].object;for(;v.parent&&v.parent!==this.scene;)v=v.parent;if(v.userData.piece){const T=A==="white"?"w":"b";if(v.userData.piece[0]!==T){St(v.userData.row,v.userData.col);return}this.dragging=v,this.dragStartPos=v.position.clone(),this.dragStartSq={row:v.userData.row,col:v.userData.col},U={row:v.userData.row,col:v.userData.col},se=Ne($,v.userData.row,v.userData.col),this.applyHighlights(),l.preventDefault();return}}}const g=this.raycaster.intersectObjects(this.squareMeshes);if(g.length>0&&$&&!$.gameOver){const y=g[0].object.userData,m=A==="black";St(m?7-y.row:y.row,m?7-y.col:y.col);return}this._orbiting=!0,a.style.cursor="grabbing",l.preventDefault()},i=l=>{const{cx:c,cy:p}=o(l),u=c-t.x,g=p-t.y;if(t={x:c,y:p},this.dragging){l.preventDefault();const y=n(l);this.mouse.set(y.x,y.y),this.raycaster.setFromCamera(this.mouse,this.camera);const m=new THREE.Vector3;this.raycaster.ray.intersectPlane(this.dragPlane,m)&&(this.dragging.position.x=m.x,this.dragging.position.z=m.z,this.dragging.position.y=.4,this.dragging.scale.setScalar(1.35));return}this._orbiting&&(l.preventDefault(),this._orbitTheta-=u*.008,this._orbitPhi-=g*.008,this._orbitPhi=Math.max(.25,Math.min(1.35,this._orbitPhi)),this._applyCameraOrbit())},r=l=>{if(this._orbiting&&(this._orbiting=!1,a.style.cursor="pointer"),this.dragging){l.preventDefault();const c=this.dragging,p=Math.round(c.position.x+3.5),u=Math.round(c.position.z+3.5),g=A==="black",y=g?7-u:u,m=g?7-p:p;if(se.some(T=>T.row===y&&T.col===m)&&this.dragStartSq){const T=this.dragStartSq,b=$.board[T.row][T.col],R=A==="white"?"w":"b";b&&b[1]==="P"&&(R==="w"&&y===0||R==="b"&&y===7)?(c.position.copy(this.dragStartPos),c.position.y=0,c.scale.setScalar(1.15),Ft(T,{row:y,col:m})):(c.position.y=0,c.scale.setScalar(1.15),yt(T,{row:y,col:m}))}else c.position.copy(this.dragStartPos),c.position.y=0,c.scale.setScalar(1.15);this.dragging.userData._dragParticles&&(this.dragging.userData._dragParticles.forEac
958h(T=>this.dragging.remove(T)),delete this.dragging.userData._dragParticles),this.dragging=null,this.dragStartPos=null,this.dragStartSq=null}},d=l=>{l.preventDefault(),this._orbitRadius+=l.deltaY*.01,this._orbitRadius=Math.max(6,Math.min(22,this._orbitRadius)),this._applyCameraOrbit()};a.addEventListener("contextmenu",l=>l.preventDefault()),a.addEventListener("mousedown",s),document.addEventListener("mousemove",i),document.addEventListener("mouseup",r),a.addEventListener("touchstart",s,{passive:!1}),document.addEventListener("touchmove",i,{passive:!1}),document.addEventListener("touchend",r,{passive:!1}),a.addEventListener("wheel",d,{passive:!1})}_loop(){requestAnimationFrame(()=>this._loop());const a=Date.now()*.001;for(const t in this.pieces){const n=this.pieces[t];n===this.dragging||n.userData._isMoving||n.userData._animFn&&n.userData._animFn(a,n)}if(this.dragging){if(this.dragging.position.y=.4+Math.sin(a*4)*.02,!this.dragging.userData._dragParticles){const t=[],n=new THREE.MeshStandardMaterial({color:16763972,emissive:16746496,emissiveIntensity:.8,roughness:.2});for(let o=0;o<6;o++){const s=new THREE.Mesh(new THREE.SphereGeometry(.015,6,6),n);s.layers.disableAll(),s.layers.enable(0),this.dragging.add(s),t.push(s)}this.dragging.userData._dragParticles=t}this.dragging.userData._dragParticles.forEach((t,n)=>{const o=a*3+n/6*Math.PI*2,s=.25+Math.sin(a*5+n)*.04;t.position.set(Math.sin(o)*s,-.1+Math.sin(a*6+n*2)*.08,Math.cos(o)*s),t.scale.setScalar(.6+Math.sin(a*8+n)*.4)})}this.renderer.render(this.scene,this.camera)}_resize(){const a=this.container.clientWidth||800,t=window.innerHeight-100,n=Math.max(400,Math.min(a*.88,t));this.container.style.height=n+"px",this.camera.aspect=a/n,this.camera.updateProjectionMatrix(),this.renderer.setSize(a,n)}dispose(){this._ro?.disconnect(),this.renderer.dispose(),this.renderer.domElement.remove()}}function ja(){return` 959 <section class="chess-page section" id="chess-page"> 960 <div class="container"> 961 <div id="chess-root"></div> 962 </div> 963 </section> 964 965 <style> 966 /* ============================================ 967 CHESS PAGE â BizWrap premium light theme 968 ============================================ */ 969 970 .chess-page { 971 min-height: calc(100vh - var(--nav-height)); 972 display: flex; 973 align-items: flex-start; 974 justify-content: center; 975 padding-top: calc(var(--nav-height) + var(--space-4)); 976 padding-bottom: var(--space-2); 977 background: var(--bg); 978 } 979 980 /* ---- Lobby ---- */ 981 .chess-lobby { 982 max-width: 480px; 983 margin: 0 auto; 984 padding: var(--space-10) var(--space-8); 985 background: var(--white); 986 border: 1px solid var(--border); 987 border-radius: var(--radius-xl); 988 box-shadow: var(--shadow-lg); 989 position: relative; 990 overflow: hidden; 991 text-align: center; 992 } 993 .chess-lobby::before { 994 content: ''; 995 position: absolute; 996 inset: 0; 997 background: linear-gradient(135deg, rgba(104, 74, 197, 0.03) 0%, rgba(1, 199, 204, 0.03) 100%); 998 pointer-events: none; 999 } 1000 .chess-lobby__title { 1001 font-family: var(--font-heading); 1002 font-size: var(--text-4xl); 1003 font-weight: var(--weight-extrabold); 1004 margin-bottom: var(--space-2); 1005 color: var(--ink); 1006 position: relative; 1007 } 1008 .chess-lobby__sub { 1009 color: var(--muted); 1010 font-size: var(--text-sm); 1011 margin-bottom: var(--space-8); 1012 position: relative; 1013 } 1014 1015 .chess-lobby__input { 1016 width: 100%; 1017 padding: var(--space-3) var(--space-4); 1018 background: var(--bg-alt); 1019 border: 1px solid var(--border); 1020 border-radius: var(--radius-md); 1021 font-size: var(--text-base); 1022 font-family: var(--font-body); 1023 color: var(--ink); 1024 margin-bottom: var(--space-5); 1025 transition: all var(--transition-fast); 1026 position: relative; 1027 } 1028 .chess-lobby__input:focus { 1029 outline: none; 1030 border-color: var(--purple); 1031 background: var(--white); 1032 box-shadow: 0 0 0 4px rgba(104, 74, 197, 0.12); 1033 } 1034 .chess-lobby__input::placeholder { color: var(--muted-light); } 1035 1036 .chess-lobby__actions { 1037 display: flex; 1038 gap: var(--space-3); 1039 margin-bottom: var(--space-6); 1040 position: relative; 1041 } 1042 .chess-lobby__actions .btn { flex: 1; } 1043 1044 .chess-lobby__pin-display { 1045 padding: var(--space-5); 1046 background: var(--bg-tint); 1047 border: 1px solid rgba(104, 74, 197, 0.15); 1048 border-radius: var(--radius-lg); 1049 margin-top: var(--space-5); 1050 position: relative; 1051 } 1052 .chess-lobby__pin-label { 1053 font-family: 'Courier New', monospace; 1054 font-size: var(--text-xs); 1055 text-transform: uppercase; 1056 letter-spacing: 0.2em; 1057 color: var(--purple); 1058 margin-bottom: var(--space-2); 1059 font-weight: var(--weight-semibold); 1060 } 1061 .chess-lobby__pin-code { 1062 font-family: 'Courier New', monospace; 1063 font-size: var(--text-5xl); 1064 font-weight: var(--weight-black); 1065 letter-spacing: 0.25em; 1066 background: var(--grad-text); 1067 -webkit-background-clip: text; 1068 background-clip: text; 1069 -webkit-text-fill-color: transparent; 1070 } 1071 .chess-lobby__pin-status { 1072 font-size: var(--text-sm); 1073 color: var(--muted); 1074 margin-top: var(--space-3); 1075 display: flex; 1076 align-items: center; 1077 justify-content: center; 1078 gap: var(--space-2); 1079 } 1080 .chess-lobby__pin-status .chess-dot { 1081 width: 8px; height: 8px; 1082 border-radius: 50%; 1083 background: var(--purple); 1084 box-shadow: 0 0 8px rgba(104, 74, 197, 0.5); 1085 animation: chess-pulse 2s ease-in-out infinite; 1086 } 1087 1088 .chess-lobby__join-section { 1089 margin-top: var(--space-5); 1090 position: relative; 1091 } 1092 .chess-lobby__join-row { 1093 display: flex; 1094 gap: var(--space-3); 1095 } 1096 .chess-lobby__join-row .chess-lobby__input { 1097 flex: 1; 1098 margin-bottom: 0; 1099 text-align: center; 1100 font-family: 'Courier New', monospace; 1101 font-size: var(--text-xl); 1102 letter-spacing: 0.2em; 1103 } 1104 1105 .chess-lobby__error {
1106 color: var(--danger); 1107 font-size: var(--text-sm); 1108 margin-top: var(--space-3); 1109 min-height: 1.4em; 1110 position: relative; 1111 } 1112 1113 /* ---- VS Computer section ---- */ 1114 .chess-lobby__vs-computer { 1115 margin-bottom: var(--space-5); 1116 position: relative; 1117 } 1118 .chess-lobby__ai-btn { 1119 width: 100%; 1120 font-size: var(--text-base); 1121 padding: var(--space-3) var(--space-5); 1122 display: flex; 1123 align-items: center; 1124 justify-content: center; 1125 } 1126 .chess-lobby__difficulty { 1127 display: flex; 1128 gap: var(--space-2); 1129 margin-top: var(--space-3); 1130 justify-content: center; 1131 } 1132 .chess-diff-btn { 1133 padding: 6px 16px; 1134 border-radius: var(--radius-full); 1135 border: 1px solid var(--border-strong); 1136 background: transparent; 1137 color: var(--muted); 1138 font-size: var(--text-xs); 1139 font-family: 'Courier New', monospace; 1140 letter-spacing: 0.1em; 1141 text-transform: uppercase; 1142 cursor: pointer; 1143 transition: all var(--transition-fast); 1144 } 1145 .chess-diff-btn:hover { 1146 border-color: var(--purple); 1147 color: var(--purple); 1148 background: var(--bg-tint); 1149 } 1150 .chess-diff-btn--active { 1151 background: var(--bg-tint); 1152 border-color: var(--purple); 1153 color: var(--purple); 1154 font-weight: var(--weight-semibold); 1155 box-shadow: 0 2px 8px rgba(104, 74, 197, 0.15); 1156 } 1157 1158 .chess-lobby__divider { 1159 display: flex; 1160 align-items: center; 1161 gap: var(--space-3); 1162 margin-bottom: var(--space-4); 1163 color: var(--muted-light); 1164 font-size: var(--text-xs); 1165 text-transform: uppercase; 1166 letter-spacing: 0.15em; 1167 position: relative; 1168 } 1169 .chess-lobby__divider::before, 1170 .chess-lobby__divider::after { 1171 content: ''; 1172 flex: 1; 1173 height: 1px; 1174 background: var(--border); 1175 } 1176 1177 /* Thinking status pulse */ 1178 .chess-status--thinking { 1179 background: var(--bg-tint); 1180 animation: chess-thinking-pulse 1.2s ease-in-out infinite; 1181 } 1182 @keyframes chess-thinking-pulse { 1183 0%, 100% { opacity: 1; } 1184 50% { opacity: 0.6; } 1185 } 1186 1187 /* ---- Game layout ---- */ 1188 .chess-game { 1189 display: flex; 1190 flex-direction: column; 1191 align-items: center; 1192 gap: 0; 1193 max-width: 900px; 1194 margin: 0 auto; 1195 width: 100%; 1196 } 1197 1198 /* ---- 3D Board Container ---- */ 1199 .chess-3d-wrap { 1200 position: relative; 1201 width: 100%; 1202 max-width: 900px; 1203 border-radius: var(--radius-lg); 1204 overflow: visible; 1205 background: transparent; 1206 } 1207 .chess-3d-wrap canvas { 1208 display: block; 1209 width: 100% !important; 1210 height: auto !important; 1211 } 1212 1213 /* ---- Top Bar (replaces sidebar) ---- */ 1214 .chess-topbar { 1215 width: 100%; 1216 max-width: 900px; 1217 background: var(--white); 1218 border: 1px solid var(--border); 1219 border-radius: var(--radius-lg); 1220 box-shadow: var(--shadow-md); 1221 margin-bottom: var(--space-3); 1222 overflow: hidden; 1223 } 1224 .chess-topbar__row { 1225 display: flex; 1226 align-items: center; 1227 gap: var(--space-4); 1228 padding: var(--space-2) var(--space-4); 1229 flex-wrap: wrap; 1230 } 1231 .chess-topbar__status { 1232 font-size: var(--text-sm); 1233 font-weight: var(--weight-semibold); 1234 color: var(--ink); 1235 background: var(--bg-tint); 1236 padding: var(--space-2) var(--space-5); 1237 text-align: center; 1238 border-bottom: 1px solid var(--border); 1239 } 1240 .chess-topbar__status.chess-status--check { 1241 background: rgba(220, 38, 38, 0.08); 1242 color: var(--danger); 1243 } 1244 .chess-topbar__status.chess-status--thinking { 1245 animation: chess-thinking-pulse 1.5s ease-in-out infinite; 1246 } 1247 .chess-topbar__pin { 1248 font-family: 'Courier New', monospace; 1249 font-size: var(--text-xs); 1250 color: var(--muted-light); 1251 letter-spacing: 0.12em; 1252 white-space: nowrap; 1253 } 1254 .chess-topbar__pin span { 1255 color: var(--purple); 1256 font-weight: var(--weight-semibold); 1257 } 1258 1259 /* Player chips */ 1260 .chess-topbar__players { 1261 display: flex; 1262 gap: var(--space-3); 1263 align-items: center; 1264 } 1265 .chess-player { 1266 display: flex; 1267 align-items: center; 1268 gap: var(--space-2); 1269 } 1270 .chess-player__dot { 1271 width: 8px; height: 8px; 1272 border-radius: 50%; 1273 background: var(--muted-light); 1274 flex-shrink: 0; 1275 } 1276 .chess-player__dot--active { 1277 background: var(--purple); 1278 box-shadow: 0 0 6px rgba(104, 74, 197, 0.4); 1279 animation: chess-pulse 2s ease-in-out infinite; 1280 } 1281 .chess-player__name { 1282 font-weight: var(--weight-semibold); 1283 color: var(--ink); 1284 font-size: var(--text-xs); 1285 white-space: nowrap; 1286 } 1287 .chess-player__color { 1288 font-family: 'Courier New', monospace; 1289 font-size: 10px; 1290 color: var(--muted-light); 1291 }
1292 @keyframes chess-pulse { 1293 0%, 100% { opacity: 1; transform: scale(1); } 1294 50% { opacity: 0.5; transform: scale(0.75); } 1295 } 1296 1297 /* Captured pieces â compact inline */ 1298 .chess-topbar__captured { 1299 display: flex; 1300 align-items: center; 1301 gap: var(--space-3); 1302 flex: 1; 1303 min-width: 0; 1304 } 1305 .chess-captured-group { 1306 display: flex; 1307 align-items: center; 1308 gap: 1px; 1309 } 1310 .chess-captured-group__label { 1311 font-family: 'Courier New', monospace; 1312 font-size: 9px; 1313 color: var(--muted-light); 1314 text-transform: uppercase; 1315 letter-spacing: 0.08em; 1316 margin-right: 4px; 1317 white-space: nowrap; 1318 } 1319 .chess-captured__piece { 1320 font-size: 0.95rem; 1321 line-height: 1; 1322 filter: drop-shadow(0 1px 1px rgba(0,0,0,0.15)); 1323 opacity: 0.75; 1324 } 1325 1326 /* Move history â horizontal scroll strip */ 1327 .chess-topbar__moves { 1328 display: flex; 1329 align-items: center; 1330 gap: var(--space-2); 1331 padding: var(--space-1) var(--space-4); 1332 border-top: 1px solid var(--border); 1333 overflow-x: auto; 1334 scrollbar-width: thin; 1335 -ms-overflow-style: none; 1336 } 1337 .chess-topbar__moves::-webkit-scrollbar { height: 3px; } 1338 .chess-topbar__moves::-webkit-scrollbar-thumb { background: var(--purple); border-radius: 2px; } 1339 .chess-topbar__moves-label { 1340 font-family: 'Courier New', monospace; 1341 font-size: 9px; 1342 color: var(--muted-light); 1343 text-transform: uppercase; 1344 letter-spacing: 0.1em; 1345 white-space: nowrap; 1346 flex-shrink: 0; 1347 } 1348 .chess-move-chip { 1349 font-family: 'Courier New', monospace; 1350 font-size: var(--text-xs); 1351 color: var(--ink-soft); 1352 background: var(--bg-alt); 1353 padding: 2px 6px; 1354 border-radius: 4px; 1355 white-space: nowrap; 1356 flex-shrink: 0; 1357 } 1358 .chess-move-chip:last-child { 1359 background: var(--bg-tint); 1360 color: var(--purple); 1361 font-weight: var(--weight-semibold); 1362 } 1363 .chess-move-chip__num { 1364 color: var(--muted-light); 1365 margin-right: 2px; 1366 } 1367 1368 /* Actions inline */ 1369 .chess-topbar__actions { 1370 display: flex; 1371 gap: var(--space-2); 1372 flex-shrink: 0; 1373 } 1374 .chess-topbar__actions .btn { 1375 font-size: 11px; 1376 padding: var(--space-1) var(--space-3); 1377 } 1378 .chess-theme-select { 1379 padding: 4px 10px; 1380 border-radius: var(--radius-md); 1381 border: 1px solid var(--border); 1382 background: var(--bg-tint); 1383 font-size: 11px; 1384 font-family: inherit; 1385 cursor: pointer; 1386 color: var(--ink); 1387 outline: none; 1388 transition: border-color 0.2s; 1389 } 1390 .chess-theme-select:focus { 1391 border-color: var(--purple); 1392 } 1393 1394 /* ---- Responsive ---- */ 1395 @media (max-width: 640px) { 1396 .chess-topbar__row { 1397 gap: var(--space-2); 1398 padding: var(--space-2) var(--space-3); 1399 } 1400 .chess-topbar__captured { display: none; } 1401 } 1402 @media (max-width: 500px) { 1403 .chess-lobby { 1404 padding: var(--space-6) var(--space-5); 1405 } 1406 } 1407 /* ---- Promotion Modal ---- */ 1408 .chess-promo-overlay { 1409 position: fixed; 1410 inset: 0; 1411 background: rgba(10, 10, 11, 0.45); 1412 backdrop-filter: blur(6px); 1413 z-index: 2000; 1414 display: flex; 1415 align-items: center; 1416 justify-content: center; 1417 } 1418 .chess-promo-modal { 1419 background: var(--white); 1420 border: 1px solid var(--border); 1421 border-radius: var(--radius-lg); 1422 padding: var(--space-6); 1423 text-align: center; 1424 box-shadow: var(--shadow-xl); 1425 } 1426 .chess-promo-modal h3 { 1427 font-size: var(--text-lg); 1428 font-family: var(--font-heading); 1429 font-weight: var(--weight-bold); 1430 color: var(--ink); 1431 margin-bottom: var(--space-4); 1432 } 1433 .chess-promo-options { 1434 display: flex; 1435 gap: var(--space-3); 1436 justify-content: center; 1437 } 1438 .chess-promo-btn { 1439 width: 64px; height: 64px; 1440 font-size: 2.5rem; 1441 display: flex; 1442 align-items: center; 1443 justify-content: center; 1444 background: var(--bg-alt); 1445 border: 1px solid var(--border); 1446 border-radius: var(--radius-md); 1447 cursor: pointer; 1448 transition: all var(--transition-fast); 1449 line-height: 1; 1450 } 1451 .chess-promo-btn:hover { 1452 border-color: var(--purple); 1453 box-shadow: var(--shadow-purple); 1454 transform: translateY(-2px); 1455 background: var(--bg-tint); 1456 } 1457 1458 /* ---- Game Over ---- */ 1459 .chess-gameover { 1460 max-width: 520px; 1461 margin: 0 auto; 1462 padding: var(--space-10) var(--space-8); 1463 background: var(--white); 1464 border: 1px solid var(--border); 1465 border-radius: var(--radius-xl); 1466 box-shadow: var(--shadow-lg); 1467 text-align: center; 1468 position: relative; 1469 overflow: hidden; 1470 } 1471 .chess-gameover::before { 1472 content: ''; 1473 position: absolute; 1474 inset: 0; 1475 background: linear-gradient(135deg, rgba(104, 74, 197, 0.03) 0%, rgba(1, 199, 204, 0.03) 100%); 1476 pointer-events: none; 1477 } 1478 .chess-gameover__result { 1479 font-family: 'Courier New', monospace; 1480 font-size: var(--text-xs); 1481 text-transform: uppercase; 1482 letter-spacing: 0.2em; 1483 color: var(--purple); 1484 margin-bottom: var(--space-2); 1485 font-weight: var(--weight-semibold); 1486 position: relative; 1487 } 1488 .chess-gameover__title { 1489 font-family: var(--font-heading); 1490 font-size: var(--text-4xl); 1491 font-weight: var(--weight-extrabold); 1492 color: var(--ink); 1493 margin-bottom: var(--space-6); 1494 position: relative; 1495 } 1496 .chess-gameover__leaderboard { 1497 margin-top: var(--space-6); 1498 text-align: left; 1499 position: relative; 1500 } 1501 .chess-gameover__lb-title { 1502 font-family: 'Courier New', monospace; 1503 font-size: var(--text-xs); 1504 text-transform: uppercase; 1505 letter-spacing: 0.15em; 1506 color: var(--purple); 1507 margin-bottom: var(--space-3); 1508 display: flex; 1509 align-items: center; 1510 gap: var(--space-2); 1511 font-weight: var(--weight-semibold); 1512 } 1513 .chess-gameover__lb-title::before { 1514 content: ''; 1515 width: 16px; height: 2px; 1516 background: var(--purple); 1517 border-radius: 1px; 1518 } 1519 .chess-gameover__lb-row { 1520 display: grid; 1521 grid-template-columns: 30px 1fr auto; 1522 gap: var(--space-2); 1523 padding: var(--space-2) var(--space-3); 1524 border-bottom: 1px solid var(--border); 1525 font-size: var(--text-sm); 1526 align-items: center; 1527 } 1528 .chess-gameover__lb-row:last-child { border-bottom: none; } 1529 .chess-gameover__lb-rank { 1530 font-family: 'Courier New', monospace; 1531 font-size: var(--text-xs); 1532 color: var(--muted-light); 1533 } 1534 .chess-gameover__lb-name { color: var(--ink); } 1535 .chess-gameover__lb-wins { 1536 font-family: 'Courier New', monospace; 1537 font-size: var(--text-xs); 1538 color: var(--purple); 1539 font-weight: var(--weight-semibold); 1540 } 1541 .chess-gameover__actions { 1542 margin-top: var(--space-6); 1543 display: flex; 1544 gap: var(--space-3); 1545 justify-content: center; 1546 position: relative; 1547 } 1548 </style> 1549 `}function Wa(){return` 1550 <div class="chess-lobby"> 1551 <h2 class="chess-lobby__title"><span class="grad-text">Chess Arena</span></h2> 1552 <p class="chess-lobby__sub">Challenge a friend or test your skill against BizWrap AI</p> 1553 1554 <input 1555 type="text" 1556 class="chess-lobby__input" 1557 id="chess-name-input" 1558 placeholder="Enter your name" 1559 maxlength="20" 1560 autocomplete="off" 1561 /> 1562 1563 <!-- Single Player --> 1564 <div class="chess-lobby__vs-computer"> 1565 <button class="btn btn--primary chess-lobby__ai-btn" id="chess-vs-computer-btn"> 1566 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-3px;margin-right:6px"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8"/><path d="M12 17v4"/><circle cx="8" cy="10" r="1"/><circle cx="16" cy="10" r="1"/><path d="M9 14c.6.9 1.6 1 3 1s2.4-.1 3-1"/></svg> 1567 Play vs Computer 1568 </button> 1569 <div class="chess-lobby__difficulty" id="chess-difficulty"> 1570 <button class="chess-diff-btn ${ue==="easy"?"chess-diff-btn--active":""}" data-diff="easy">Easy</button> 1571 <button class="chess-diff-btn ${ue==="medium"?"chess-diff-btn--active":""}" data-diff="medium">Medium</button> 1572 <button class="chess-diff-btn ${ue==="hard"?"chess-diff-btn--active":""}" data-diff="hard">Hard</button> 1573 </div> 1574 </div> 1575 1576 <div class="chess-lobby__divider">
1577 <span>or play online</span> 1578 </div> 1579 1580 <div class="chess-lobby__actions"> 1581 <button class="btn btn--ghost" id="chess-create-btn">Create Game</button> 1582 <button class="btn btn--ghost" id="chess-show-join-btn">Join Game</button> 1583 </div> 1584 1585 <div id="chess-pin-area"></div> 1586 <div id="chess-join-area"></div> 1587 <div class="chess-lobby__error" id="chess-lobby-error"></div> 1588 </div> 1589 `}function Ua(e){return` 1590 <div class="chess-lobby__pin-display"> 1591 <div class="chess-lobby__pin-label">Game PIN</div> 1592 <div class="chess-lobby__pin-code">${e}</div> 1593 <div class="chess-lobby__pin-status"> 1594 <span class="chess-dot"></span> 1595 Waiting for opponent⦠1596 </div> 1597 </div> 1598 `}function Ya(){return` 1599 <div class="chess-lobby__join-section"> 1600 <div class="chess-lobby__join-row"> 1601 <input 1602 type="text" 1603 class="chess-lobby__input" 1604 id="chess-pin-input" 1605 placeholder="PIN" 1606 maxlength="4" 1607 autocomplete="off" 1608 /> 1609 <button class="btn btn--primary" id="chess-join-btn">Join</button> 1610 </div> 1611 </div> 1612 `}function Va(){if(!$)return"";const e=$.turn,a=e===A,t=Tt($.captured.white),n=Tt($.captured.black);let o=a?"Your turn":`${X}'s turn`;j&&ae&&(o="BizWrap AI is thinkingâ¦");let s="chess-status";ve($,e)&&(o=a?"You are in check!":`${X} is in check!`,s+=" chess-status--check"),j&&ae&&(s+=" chess-status--thinking");let i="";for(let r=0;r<$.moveHistory.length;r+=2){const d=Math.floor(r/2)+1,l=$.moveHistory[r]||"",c=$.moveHistory[r+1]||"";i+=`<span class="chess-move-chip"><span class="chess-move-chip__num">${d}.</span>${l}</span>`,c&&(i+=`<span class="chess-move-chip"><span class="chess-move-chip__num">${d}â¦</span>${c}</span>`)}return` 1613 <div class="chess-game"> 1614 <div class="chess-topbar"> 1615 <div class="chess-topbar__status ${s}" id="chess-status">${o}</div> 1616 <div class="chess-topbar__row"> 1617 <div class="chess-topbar__pin">${j?"VS <span>BIZWRAP AI</span> · "+ue.toUpperCase():"GAME <span>#"+ke+"</span>"}</div> 1618 <div class="chess-topbar__players"> 1619 <div class="chess-player"> 1620 <span class="chess-player__dot ${e==="white"?"chess-player__dot--active":""}"></span> 1621 <span class="chess-player__name">${A==="white"?te:X}</span> 1622 <span class="chess-player__color">W</span> 1623 </div> 1624 <div class="chess-player"> 1625 <span class="chess-player__dot ${e==="black"?"chess-player__dot--active":""}"></span> 1626 <span class="chess-player__name">${A==="black"?te:X}</span> 1627 <span class="chess-player__color">B</span> 1628 </div> 1629 </div> 1630 <div class="chess-topbar__captured"> 1631 <div class="chess-captured-group"> 1632 <span class="chess-captured-group__label">W:</span> 1633 ${t.map(r=>`<span class="chess-captured__piece">${He[r]}</span>`).join("")} 1634 </div> 1635 <div class="chess-captured-group"> 1636 <span class="chess-captured-group__label">B:</span> 1637 ${n.map(r=>`<span class="chess-captured__piece">${He[r]}</span>`).join("")} 1638 </div> 1639 </div> 1640 <div class="chess-topbar__actions"> 1641 <select class="chess-theme-select" id="chess-theme-select"> 1642 <option value="classic"${N?.theme==="classic"?" selected":""}>â Classic</option> 1643 <option value="alien"${N?.theme==="alien"?" selected":""}>ð½ Alien</option> 1644 <option value="christmas"${N?.theme==="christmas"?" selected":""}>ð Christmas</option> 1645 <option value="halloween"${N?.theme==="halloween"?" selected":""}>ð Halloween</option> 1646 </select> 1647 <button class="btn btn--ghost" id="chess-resign-btn">Resign</button> 1648 <button class="btn btn--ghost" id="chess-draw-btn">Draw</button> 1649 </div> 1650 </div> 1651 ${$.moveHistory.length>0?` 1652 <div class="chess-topbar__moves" id="chess-moves"> 1653 <span class="chess-topbar__moves-label">MOVES</span> 1654 ${i} 1655 </div> 1656 `:""} 1657 </div> 1658 1659 <div class="chess-3d-wrap" id="chess-3d-container"></div> 1660 </div> 1661 `}function Ja(){const e=$?.result;if(!e)return"";let a="",t="";switch(e.type){case"checkmate":a="Checkmate",e.winner===A?t='<span class="grad-text">You Win!</span>':j?t=`<span class="grad-text">You've Been Gift Wrapped!</span>`:t=`<span class="grad-text">${X} Wins</span>`;break;case"stalemate":a="Stalemate",t='<span class="grad-text">Draw</span>';break;case"resignation":a="Resignation",e.winner===A?t='<span class="grad-text">You Win!</span>':j?t=`<span class="grad-text">You've Been Gift Wrapped!</span>`:t=`<span class="grad-text">${X} Wins</span>`;break;case"draw":a=e.reason||"Draw",t='<span class="grad-text">Draw</span>';break;default:a="Game Over",t='<span class="grad-text">Game Over</span>'}let n="";return Le.length>0&&(n=Le.slice(0,10).map((o,s)=>` 1662 <div class="chess-gameover__lb-row">
1663 <span class="chess-gameover__lb-rank">#${s+1}</span> 1664 <span class="chess-gameover__lb-name">${Ka(o.name)}</span> 1665 <span class="chess-gameover__lb-wins">${o.wins} W</span> 1666 </div> 1667 `).join("")),` 1668 <div class="chess-gameover"> 1669 <div class="chess-gameover__result">${a}</div> 1670 <h2 class="chess-gameover__title">${t}</h2> 1671 1672 ${e.winner===A?` 1673 <div style="margin-bottom:var(--space-4)"> 1674 <input 1675 type="text" 1676 class="chess-lobby__input" 1677 id="chess-lb-name" 1678 placeholder="Name for leaderboard" 1679 value="${Qa(te)}" 1680 maxlength="20" 1681 /> 1682 <button class="btn btn--primary" id="chess-lb-submit">Submit to Leaderboard</button> 1683 </div> 1684 `:""} 1685 1686 ${Le.length>0?` 1687 <div class="chess-gameover__leaderboard"> 1688 <div class="chess-gameover__lb-title">Top Players</div> 1689 ${n} 1690 </div> 1691 `:""} 1692 1693 <div class="chess-gameover__actions"> 1694 <button class="btn btn--primary" id="chess-play-again">Play Again</button> 1695 </div> 1696 </div> 1697 `}function Ka(e){const a=document.createElement("div");return a.textContent=e,a.innerHTML}function Qa(e){return e.replace(/"/g,""").replace(/'/g,"'")}function Tt(e){return[...e].sort((a,t)=>(Mt[a[1]]||0)-(Mt[t[1]]||0))}function F(){const e=document.getElementById("chess-root");if(!e)return;const a=N?.renderer?.domElement;switch(a?.parentNode&&a.parentNode.removeChild(a),K){case"lobby":N&&(N.dispose(),N=null),e.innerHTML=Wa(),as();break;case"game":{e.innerHTML=Va();const t=document.getElementById("chess-3d-container");t&&Ze&&(N?(t.appendChild(N.renderer.domElement),N.container=t,N._resize()):N=new Fa(t),$&&(N.sync($,A),N.applyHighlights())),ns();break}case"gameover":N&&(N.dispose(),N=null),e.innerHTML=Ja(),os();break}}function gt(e,a,t,n){N?N.animateMove3D(e,a,t,n):n()}function ce(e){const a=document.getElementById("chess-lobby-error");a&&(a.textContent=e)}function Ot(){const e=window.location.protocol==="https:"?"wss:":"ws:",a=window.location.hostname||"localhost",n=`${e}//${a}:5000/chess`;Y=new WebSocket(n),Y.onopen=()=>{console.log("[Chess] WebSocket connected")},Y.onmessage=o=>{let s;try{s=JSON.parse(o.data)}catch{return}Xa(s)},Y.onclose=()=>{console.log("[Chess] WebSocket closed"),Y=null},Y.onerror=o=>{console.error("[Chess] WebSocket error",o),ce("Cannot connect to server. Make sure the backend is running on port 5000.")}}function Me(e){Y&&Y.readyState===WebSocket.OPEN&&Y.send(JSON.stringify(e))}function Xa(e){switch(e.type){case"created":ke=e.pin,A=e.color||"white";const a=document.getElementById("chess-pin-area");a&&(a.innerHTML=Ua(e.pin));break;case"joined":A=e.color||"black",X=e.opponentName||"Opponent",$=Qe(),K="game",F();break;case"opponent-joined":X=e.opponentName||"Opponent",$=Qe(),K="game",F();break;case"move":{const{from:t,to:n,promotion:o}=e;if($&&!$.gameOver){const s=$.board[t.row][t.col];gt(t,n,s,()=>{$=at($,t.row,t.col,n.row,n.col,o),U=null,se=[],Be={row:t.row,col:t.col},Pe={row:n.row,col:n.col},$.gameOver&&(K="gameover",Ee()),F();const i=document.getElementById("chess-moves");i&&(i.scrollTop=i.scrollHeight)})}break}case"opponent-resigned":$&&($.gameOver=!0,$.result={type:"resignation",winner:A},K="gameover",Ee(),F());break;case"draw-offered":confirm(`${X} offers a draw. Accept?`)?(Me({type:"draw-accept"}),$&&($.gameOver=!0,$.result={type:"draw",reason:"agreement",winner:null},K="gameover",Ee(),F())):Me({type:"draw-decline"});break;case"draw-accepted":$&&($.gameOver=!0,$.result={type:"draw",reason:"agreement",winner:null},K="gameover",Ee(),F());break;case"error":ce(e.message||"An error occurred");break}}async function Ee(){try{const e=await fetch("/api/chess/leaderboard");e.ok&&(Le=await e.json())}catch{Le=[]}}async function Za(e){try{await fetch("/api/chess/leaderboard",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e})}),await Ee(),F()}catch{}}let Ye=null;function Ft(e,a){Ye={from:e,to:a};const t=A==="white"?"w":"b",n=[{type:"Q",symbol:He[t+"Q"]},{type:"R",symbol:He[t+"R"]},{type:"B",symbol:He[t+"B"]},{type:"N",symbol:He[t+"N"]}],o=document.createElement("div");
1697o.className="chess-promo-overlay",o.id="chess-promo-overlay",o.innerHTML=` 1698 <div class="chess-promo-modal"> 1699 <h3>Promote Pawn</h3> 1700 <div class="chess-promo-options"> 1701 ${n.map(s=>` 1702 <button class="chess-promo-btn" data-promo="${s.type}">${s.symbol}</button> 1703 `).join("")} 1704 </div> 1705 </div> 1706 `,document.body.appendChild(o),o.addEventListener("click",s=>{const i=s.target.closest("[data-promo]");if(i){const r=i.dataset.promo;yt(Ye.from,Ye.to,r),Ye=null,o.remove()}})}function St(e,a){if(!$||$.gameOver||$.turn!==A||j&&ae||Xe)return;const t=$.board[e][a],n=A==="white"?"w":"b";if(t&&t[0]===n){U={row:e,col:a},se=Ne($,e,a),F();return}if(U)if(se.some(s=>s.row===e&&s.col===a)){const s=$.board[U.row][U.col];s&&s[1]==="P"&&(n==="w"&&e===0||n==="b"&&e===7)?Ft(U,{row:e,col:a}):yt(U,{row:e,col:a})}else U=null,se=[],F()}function yt(e,a,t){if(!$)return;const n=$.board[e.row][e.col];Xe=!0,gt(e,a,n,()=>{$=at($,e.row,e.col,a.row,a.col,t),U=null,se=[],Be={row:e.row,col:e.col},Pe={row:a.row,col:a.col},Xe=!1,j||Me({type:"move",from:{row:e.row,col:e.col},to:{row:a.row,col:a.col},promotion:t||void 0}),$.gameOver&&(K="gameover",j||Ee()),F();const o=document.getElementById("chess-moves");o&&(o.scrollTop=o.scrollHeight),j&&!$.gameOver&&$.turn!==A&&ts()})}function es(){switch(ue){case"easy":return 1;case"hard":return 4;default:return 3}}function ts(){ae=!0,F(),setTimeout(()=>{if(!$||$.gameOver||$.turn===A){ae=!1;return}const a=Na($,es());if(a){const t=$.board[a.from.row][a.from.col];gt(a.from,a.to,t,()=>{$=at($,a.from.row,a.from.col,a.to.row,a.to.col,a.promotion),Be={row:a.from.row,col:a.from.col},Pe={row:a.to.row,col:a.to.col},ae=!1,$.gameOver&&(K="gameover"),F();const n=document.getElementById("chess-moves");n&&(n.scrollTop=n.scrollHeight)})}else ae=!1,F()},ue==="easy"?350:ue==="hard"?800:500)}function as(){const e=document.getElementById("chess-name-input"),a=document.getElementById("chess-create-btn"),t=document.getElementById("chess-show-join-btn"),n=document.getElementById("chess-vs-computer-btn"),o=document.querySelectorAll(".chess-diff-btn");o.forEach(s=>{s.addEventListener("click",()=>{ue=s.dataset.diff,o.forEach(i=>i.classList.remove("chess-diff-btn--active")),s.classList.add("chess-diff-btn--active")})}),n?.addEventListener("click",()=>{te=e?.value.trim()||"Player",ce(""),j=!0,X="BizWrap AI",A="white",ke="AI",$=Qe(),K="game",F()}),a?.addEventListener("click",()=>{if(te=e?.value.trim()||"",!te){ce("Please enter your name");return}ce(""),j=!1,Ot();const s=setInterval(()=>{Y&&Y.readyState===WebSocket.OPEN&&(clearInterval(s),Me({type:"create",playerName:te}))},100)}),t?.addEventListener("click",()=>{if(te=e?.value.trim()||"",!te){ce("Please enter your name");return}ce(""),j=!1;const s=document.getElementById("chess-join-area");s&&(s.innerHTML=Ya(),ss())})}function ss(){const e=document.getElementById("chess-join-btn"),a=document.getElementById("chess-pin-input");e?.addEventListener("click",()=>{const t=a?.value.trim()||"";if(!t||t.length<4){ce("Please enter a valid 4-digit PIN");return}ce(""),ke=t,Ot();const n=setInterval(()=>{Y&&Y.readyState===WebSocket.OPEN&&(clearInterval(n),Me({type:"join",pin:t,playerName:te}))},100)}),a?.addEventListener("keydown",t=>{t.key==="Enter"&&e?.click()})}function ns(){document.getElementById("chess-resign-btn")?.addEventListener("click",()=>{confirm("Are you sure you want to resign?")&&(j||Me({type:"resign"}),$&&($.gameOver=!0,$.result={type:"resignation",winner:A==="white"?"black":"white"},K="gameover",j||Ee(),F()))});const a=document.getElementById("chess-draw-btn");a?.addEventListener("click",()=>{if(j){a.textContent="Declined!",a.disabled=!0,a.style.opacity="0.5";return}Me({type:"draw-offer"}),a.textContent="Draw Offered",a.disabled=!0,a.style.opacity="0.5"}),document.getElementById("chess-theme-select")?.addEventListener("change",n=>{N&&N.setTheme(n.target.value)})}function os(){document.getElementById("chess-play-again")?.addEventListener("click",()=>{if(j){$=Qe(),A="white",X="BizWrap AI",ke="AI",U=null,se=[],ae=!1,K="game",F();return}
1706$=null,A=null,X="",ke="",U=null,se=[],j=!1,ae=!1,Y&&(Y.close(),Y=null),K="lobby",F()});const a=document.getElementById("chess-lb-submit");a?.addEventListener("click",()=>{const n=document.getElementById("chess-lb-name")?.value.trim()||te;n&&(Za(n),a.textContent="Submitted!",a.disabled=!0,a.style.opacity="0.5")})}function is(){K="lobby",$=null,A=null,te="",X="",ke="",U=null,se=[],Le=[],j=!1,ae=!1,ue="medium",Ze||Oa().then(()=>F()).catch(e=>console.warn("Three.js load failed:",e)),F()}function rs(){return` 1707 <section class="hero hero--page hero--compact"> 1708 <div class="hero__grid hero__grid--center"> 1709 <div class="hero__copy"> 1710 <h1 class="hero__title"><span class="grad-text">Business</span> Discovery</h1> 1711 <p class="hero__subtitle"> 1712 Please tell us about your operation so we can prepare for our discovery meeting. 1713 </p> 1714 </div> 1715 </div> 1716 </section> 1717 1718 <section class="section" style="padding-top: var(--space-4); padding-bottom: var(--space-16);"> 1719 <div class="container container--narrow"> 1720 <div class="form-card" style="max-width: 720px; margin: 0 auto;"> 1721 1722 <!-- PROGRESS BAR --> 1723 <div class="q-progress-container" style="margin-bottom: var(--space-8);"> 1724 <div style="display: flex; justify-content: space-between; font-size: var(--text-xs); font-weight: var(--weight-bold); text-transform: uppercase; color: var(--muted); margin-bottom: var(--space-2);"> 1725 <span id="q-step-indicator">Step 1 of 4: Business Profile</span> 1726 <span id="q-percent-indicator">25% Complete</span> 1727 </div> 1728 <div style="height: 6px; background: var(--border); border-radius: var(--radius-full); overflow: hidden;"> 1729 <div id="q-progress-bar" style="width: 25%; height: 100%; background: var(--grad-brand); transition: width 0.3s ease;"></div> 1730 </div> 1731 </div> 1732 1733 <form id="questionnaire-form"> 1734 <!-- STEP 1: General Business Profile --> 1735 <div class="q-step" id="q-step-1"> 1736 <h3 style="margin-bottom: var(--space-6); font-family: var(--font-heading);">1. General Business Profile</h3> 1737 1738 <!-- Conditional Email Input (only if clientId is missing) --> 1739 <div class="form-group" id="q-email-group" style="display: none;"> 1740 <label class="form-label" for="q-email">Email Address <span style="color:var(--danger)">*</span></label> 1741 <input class="form-input" type="email" id="q-email" placeholder="[email protected]" /> 1742 <span class="form-help" style="font-size:11px; color:var(--muted)">Enter the same email address you used in the contact form so we can link your responses.</span> 1743 </div> 1744 1745 <!-- COMPANY BASICS â these power a live demo of your OS, branded as YOUR company --> 1746 <div style="background: var(--surface-2, rgba(255,255,255,0.03)); border:1px solid var(--border); border-radius: var(--radius-lg, 14px); padding: var(--space-5); margin-bottom: var(--space-6);"> 1747 <div style="font-size:var(--text-xs); font-weight:var(--weight-bold); text-transform:uppercase; letter-spacing:0.06em; color:var(--brand, #d9b25a); margin-bottom: var(--space-2);">Company Basics</div> 1748 <p style="font-size:var(--text-xs); color:var(--muted); margin-bottom: var(--space-4); line-height:1.5;"> 1749 We use these to build a working demo of your operating system â branded as <em>your</em> company â to walk through together on our discovery call. 1750 </p> 1751 1752 <div class="form-group"> 1753 <label class="form-label" for="q-company-name">Company name <span style="color:var(--danger)">*</span></label> 1754 <input class="form-input" type="text" id="q-company-name" placeholder="e.g. Acme Holiday Lighting" required /> 1755 </div> 1756 1757 <div class="form-group"> 1758 <label class="form-label" for="q-slogan">Company slogan <span style="color:var(--danger)">*</span></label> 1759 <input class="form-input" type="text" id="q-slogan" placeholder="e.g. Light Up The Night" required />
1760 <span class="form-help" style="font-size:11px; color:var(--muted)">Your tagline â shown front-and-center on your demo dashboard.</span> 1761 </div> 1762 1763 <div class="form-group"> 1764 <label class="form-label" for="q-owner-name">Your first name <span style="color:var(--danger)">*</span></label> 1765 <input class="form-input" type="text" id="q-owner-name" placeholder="e.g. Sam" required /> 1766 </div> 1767 1768 <div class="form-group"> 1769 <label class="form-label" for="q-owner-email">Best email for you (the owner) <span style="color:var(--danger)">*</span></label> 1770 <input class="form-input" type="email" id="q-owner-email" placeholder="[email protected]" required /> 1771 <span class="form-help" id="q-owner-email-hint" style="display:none; font-size:11px; color:var(--muted)">Use the same email you used to contact us, so we can match your responses.</span> 1772 </div> 1773 1774 <div class="form-group"> 1775 <label class="form-label" for="q-website">Company website <span style="color:var(--danger)">*</span></label> 1776 <input class="form-input" type="text" id="q-website" placeholder="e.g. acmelights.com" required /> 1777 </div> 1778 1779 <div class="form-group"> 1780 <label class="form-label" for="q-city">City & state you serve <span style="color:var(--danger)">*</span></label> 1781 <input class="form-input" type="text" id="q-city" placeholder="e.g. Austin, TX" required /> 1782 </div> 1783 1784 <div class="form-group"> 1785 <label class="form-label" for="q-brand-color">Brand color (optional)</label> 1786 <input type="color" id="q-brand-color" value="#d9b25a" style="height:44px; padding:4px; width:80px; border:1px solid var(--border); border-radius: var(--radius-md, 8px); background:transparent; cursor:pointer; display:block;" /> 1787 <span class="form-help" style="font-size:11px; color:var(--muted)">Your main brand color â used to theme your demo.</span> 1788 </div> 1789 1790 <div class="form-group" style="margin-bottom:0;"> 1791 <label class="form-label" for="q-logo-file">Your logo (optional)</label> 1792 <input class="form-input" type="file" id="q-logo-file" accept="image/*" /> 1793 <span class="form-help" style="font-size:11px; color:var(--muted)">Upload your logo, or paste a link to it below â we can always add it later.</span> 1794 <div id="q-logo-preview" style="margin-top: var(--space-3); display:none;"> 1795 <img id="q-logo-img" alt="Logo preview" style="max-height:64px; max-width:220px; border-radius:8px; background:#fff; padding:6px;" /> 1796 </div> 1797 <input class="form-input" type="text" id="q-logo-url" placeholder="â¦or paste a logo URL (https://â¦)" style="margin-top: var(--space-3);" /> 1798 </div> 1799 </div> 1800 1801 <div class="form-group"> 1802 <label class="form-label" for="q-years">How many years have you been in business? <span style="color:var(--danger)">*</span></label> 1803 <input class="form-input" type="text" id="q-years" placeholder="e.g. 5 years" required /> 1804 </div> 1805 1806 <div class="form-group"> 1807 <label class="form-label" for="q-niche">What is your primary industry/niche? <span style="color:var(--danger)">*</span></label> 1808 <input class="form-input" type="text" id="q-niche" placeholder="e.g. Christmas Lighting & Landscaping" required /> 1809 </div> 1810 1811 <div class="form-group"> 1812 <label class="form-label" for="q-revenue">
1812What is your current annual revenue range? <span style="color:var(--danger)">*</span></label> 1813 <select class="form-select" id="q-revenue" required> 1814 <option value="" disabled selected>Select revenue range...</option> 1815 <option value="under-250k">Under $250k</option> 1816 <option value="250k-1m">$250k - $1M</option> 1817 <option value="1m-3m">$1M - $3M</option> 1818 <option value="3m-10m">$3M - $10M</option> 1819 <option value="10m-plus">$10M+</option> 1820 </select> 1821 </div> 1822 1823 <div class="form-group"> 1824 <label class="form-label" for="q-revenue-goal">What is your target revenue goal for the next 12-24 months? <span style="color:var(--danger)">*</span></label> 1825 <input class="form-input" type="text" id="q-revenue-goal" placeholder="e.g. Grow to $2M" required /> 1826 </div> 1827 1828 <div class="form-group"> 1829 <label class="form-label" for="q-office-staff">How many full-time office staff members do you employ? <span style="color:var(--danger)">*</span></label> 1830 <input class="form-input" type="number" id="q-office-staff" min="0" placeholder="e.g. 3" required /> 1831 </div> 1832 1833 <div class="form-group"> 1834 <label class="form-label" for="q-field-crews">How many field crews or technicians do you run in peak season? <span style="color:var(--danger)">*</span></label> 1835 <input class="form-input" type="number" id="q-field-crews" min="0" placeholder="e.g. 8" required /> 1836 </div> 1837 </div> 1838 1839 <!-- STEP 2: Current Tech Stack & Software --> 1840 <div class="q-step" id="q-step-2" style="display: none;"> 1841 <h3 style="margin-bottom: var(--space-6); font-family: var(--font-heading);">2. Current Tech Stack & Software</h3> 1842 1843 <div class="form-group"> 1844 <label class="form-label" for="q-crm">What CRM or field service software do you currently use? <span style="color:var(--danger)">*</span></label> 1845 <input class="form-input" type="text" id="q-crm" placeholder="e.g. Jobber, ServiceTitan, spreadsheets" required /> 1846 </div> 1847 1848 <div class="form-group"> 1849 <label class="form-label" for="q-design-tool">What software do you use for sales / design / estimates? <span style="color:var(--danger)">*</span></label> 1850 <input class="form-input" type="text" id="q-design-tool" placeholder="e.g. Photoshop, Canva, none" required /> 1851 </div> 1852 1853 <div class="form-group"> 1854 <label class="form-label" for="q-accounting">What accounting / financial tool do you use? <span style="color:var(--danger)">*</span></label> 1855 <input class="form-input" type="text" id="q-accounting" placeholder="e.g. QuickBooks Online, Xero" required /> 1856 </div> 1857 1858 <div class="form-group"> 1859 <label class="form-label" for="q-tech-frustrations">What is your biggest frustration with your current software stack? <span style="color:var(--danger)">*</span></label> 1860 <textarea class="form-textarea" id="q-tech-frustrations" placeholder="e.g. Systems don't talk to each other, lots of manual entries, double booking..." required></textarea> 1861 </div> 1862 </div> 1863 1864 <!-- STEP 3: Operations & Friction Points --> 1865 <div class="q-step" id="q-step-3" style="display: none;"> 1866 <h3 style="margin-bottom: var(--space-6); font-family: var(--font-heading);">3. Operations & Friction Points</h3> 1867 1868 <div class="form-group"> 1869 <label class="form-label" for="q-bottleneck">What is the single biggest bottleneck in your operations today? <span style="color:var(--danger)">*</span></label> 1870 <textarea class="form-textarea" id="q-bottleneck" placeholder="Describe where things slow down..." required></textarea> 1871 </div> 1872 1873 <div class="form-group"> 1874 <label class="form-label" for="q-time-waste">How much time per week does your office team waste copy/pasting data between tools? <span style="color:var(--danger)">*</span></label> 1875 <select class="form-select" id="q-time-waste" required> 1876 <option value="" disabled selected>Select estimated time waste...</option> 1877 <option value="0-2">0 - 2 hours / week</option> 1878 <option value="2-5">2 - 5 hours / week</option> 1879 <option value="5-10">5 - 10 hours / week</option> 1880 <option value="10-20">10 - 20 hours / week</option> 1881 <option value="20-plus">20+ hours / week</option> 1882 </select> 1883 </div> 1884 1885 <div class="form-group"> 1886 <label class="form-label" for="q-crew-tracking">How do you currently track crew progress and job status in the field? <span style="color:var(--danger)">*</span></label> 1887 <input class="form-input" type="text" id="q-crew-tracking" placeholder="e.g. Calls, texts, paper sheets, Jobber app" required /> 1888 </div> 1889 1890 <div class="form-group"> 1891 <label class="form-label" for="q-lead-slip">What percentage of your leads do you feel slip through the cracks? <span style="color:var(--danger)">*</span></label> 1892 <select class="form-select" id="q-lead-slip" required> 1893 <option value="" disabled selected>Select estimated percentage...</option> 1894 <option value="under-10">Under 10%</option> 1895 <option value="10-25">10% - 25%</option> 1896 <option value="25-50">25% - 50%</option> 1897 <option value="over-50">Over 50%</option> 1898 </select> 1899 </div> 1900 1901 <div class="form-group"> 1902 <label class="form-label" for="q-comms">How do you handle client communication (SMS, Email, Call) & is it centralized? <span style="color:var(--danger)">*</span></label> 1903 <input class="form-input" type="text" id="q-comms" placeholder="e.g. Separate personal phones, Gmail, not centralized" required /> 1904 </div> 1905 </div> 1906 1907 <!-- STEP 4: AI & Custom OS Goals --> 1908 <div class="q-step" id="q-step-4" style="display: none;"> 1909 <h3 style="margin-bottom: var(--space-6); font-family: var(--font-heading);">
19094. AI & Custom OS Goals</h3> 1910 1911 <div class="form-group"> 1912 <label class="form-label" for="q-os-goal">What is the main outcome you want to achieve with a custom AI Operating System? <span style="color:var(--danger)">*</span></label> 1913 <textarea class="form-textarea" id="q-os-goal" placeholder="e.g. Automate scheduling, centralize communication, visual design board..." required></textarea> 1914 </div> 1915 1916 <div class="form-group"> 1917 <label class="form-label" for="q-agent-use">If you could build one "AI Agent" to take over one repetitive daily task, what would it do? <span style="color:var(--danger)">*</span></label> 1918 <textarea class="form-textarea" id="q-agent-use" placeholder="e.g. Draft proposals, route crews, answer basic client queries..." required></textarea> 1919 </div> 1920 1921 <div class="form-group"> 1922 <label class="form-label" for="q-tech-comfort">How comfortable is your team with adopting new technology? (1 = Low, 10 = High) <span style="color:var(--danger)">*</span></label> 1923 <input class="form-input" type="number" id="q-tech-comfort" min="1" max="10" placeholder="e.g. 7" required /> 1924 </div> 1925 1926 <div class="form-group"> 1927 <label class="form-label" for="q-launch-timeline">What is your desired timeline for launching the custom operating system? <span style="color:var(--danger)">*</span></label> 1928 <select class="form-select" id="q-launch-timeline" required> 1929 <option value="" disabled selected>Select desired timeline...</option> 1930 <option value="immediate">As soon as possible (< 1 month)</option> 1931 <option value="1-3-months">1 - 3 months</option> 1932 <option value="3-6-months">3 - 6 months</option> 1933 <option value="flexible">Flexible / planning for next season</option> 1934 </select> 1935 </div> 1936 1937 <div class="form-group"> 1938 <label class="form-label" for="q-additional">Any additional details or specific workflows you'd like us to review? (Optional)</label> 1939 <textarea class="form-textarea" id="q-additional" placeholder="Any specific requirements..."></textarea> 1940 </div> 1941 </div> 1942 1943 <!-- BUTTONS --> 1944 <div style="display: flex; justify-content: space-between; margin-top: var(--space-8); padding-top: var(--space-5); border-top: 1px solid var(--border);"> 1945 <button class="btn btn--secondary" type="button" id="q-prev-btn" style="visibility: hidden;"> 1946 â Back 1947 </button> 1948 <button class="btn btn--primary" type="button" id="q-next-btn" style="background: var(--grad-brand);"> 1949 Next Step â 1950 </button> 1951 <button class="btn btn--primary" type="submit" id="q-submit-btn" style="display: none; background: var(--grad-brand);"> 1952 Submit Profile â 1953 </button> 1954 </div> 1955 1956 <div id="q-status" class="form-status" style="margin-top: var(--space-4);"></div> 1957 </form> 1958 1959 </div> 1960 </div> 1961 </section> 1962 `}function ls(){const e=window.location.hash,t=new URLSearchParams(e.includes("?")?e.split("?")[1]:"").get("id")||"";if(!t){const b=document.getElementById("q-owner-email-hint");b&&(b.style.display="block")}let n="";const o=document.getElementById("q-logo-file");o&&o.addEventListener("change",()=>{const b=o.files&&o.files[0];if(!b){n="";return}const R=new FileReader;R.onload=()=>{const M=new Image;M.onload=()=>{let{width:_,height:I}=M;if(_>512||I>512){const C=Math.min(512/_,512/I);_=Math.round(_*C),I=Math.round(I*C)}const E=document.createElement("canvas");E.width=_,E.height=I,E.getContext("2d").drawImage(M,0,0,_,I);let h=E.toDataURL("image/png");h.length>45e4&&(h=E.toDataURL("image/jpeg",.82)),n=h.length>7e5?"":h;const S=document.getElementById("q-logo-preview"),x=document.getElementById("q-logo-img");x&&(x.src=n||M.src),S&&(S.style.display=n?"block":"none")},M.src=R.result},R.readAsDataURL(b)});let s=1;const i=4,r=[{el:document.getElementById("q-step-1"),label:"Step 1 of 4: Business Profile",progress:25},{el:document.getElementById("q-step-2"),label:"Step 2 of 4: Tech Stack & Software",progress:50},{el:document.getElementById("q-step-3"),label:"Step 3 of 4: Operations & Friction",progress:75},{el:document.getElementById("q-step-4"),label:"Step 4 of 4: AI & OS Goals",progress:100}],d=document.getElementById("q-next-btn"),l=document.getElementById("q-prev-btn"),c=document.getElementById("q-submit-btn"),p=document.getElementById("q-step-indicator"),u=document.getElementById("q-percent-indicator"),g=document.getElementById("q-progress-bar"),y=document.getElementById("questionnaire-form"),m=document.getElementById("q-status");function v(){r.forEach((R,M)=>{M+1===s?(R.el.style.display="block",p&&(p.textContent=R.label),u&&(u.textContent=`${R.progress}% Complete`),g&&(g.style.width=`${R.progress}%`)):R.el.style.display="none"}),s===1?l&&(l.style.visibility="hidden"):l&&(l.style.visibility="visible"),s===i?(d&&(d.style.display="none"),c&&(c.style.display="inline-block")):(d&&(d.style.display="inline-block"),c&&(c.style.display="none"));const b=document.querySelector(".form-card");b&&b.scrollIntoView({behavior:"smooth",block:"start"})}function T(){const R=r[s-1].el.querySelectorAll("input[required], select[required], textarea[required]");let M=!0;return R.forEach(k=>{k.classList.remove("form-input--error"),k.value.trim()||(M=!1,k.classList.add("form-input--error"))}),M?m&&(m.textContent="",m.className="form-status"):m&&(m.textContent="Please fill out all required fields marked in red.",m.className="form-status form-status--error"),M}d&&d.addEventListener("click",()=>{T()&&s<i&&(s++,v())}),l&&l.addEventListener("click",()=>{s>1&&(s--,v())}),y&&y.addEventListener("submit",async b=>{if(b.preventDefault(),!T())return;c&&(c.disabled=!0),c&&(c.textContent="Submitting...");const R={questionnaire:{yearsInBusiness:document.getElementById("q-years")?.value||"",industryNiche:document.getElementById("q-niche")?.value||"",revenueRange:document.getElementById("q-revenue")?.value||"",revenueGoal:document.getElementById("q-revenue-goal")?.value||"",officeStaffCount:parseInt(document.getElementById("q-office-staff")?.value||"0",10),fieldCrewCount:parseInt(document.getElementById("q-field-crews")?.value||"0",10),currentCrm:document.getElementById("q-crm")?.value||"",designTool:document.getElementById("q-design-tool")?.value||"",accountingTool:document.getElementById("q-accounting")?.value||"",techFrustrations:document.getElementById("q-tech-frustrations")?.value||"",biggestBottleneck:document.getElementById("q-bottleneck")?.value||"",timeWasteCopyPaste:document.getElementById("q-time-waste")?.value||"",crewTrackingMode:document.getElementById("q-crew-tracking")?.value||"",leadSlipPercentage:document.getElementById("q-lead-slip")?.value||"",clientCommsCentralized:document.getElementById("q-comms")?.value||"",osGoalOutcome:document.getElementById("q-os-goal")?.value||"",desiredAgentTask:document.getElementById("q-agent-use")?.value||"",techAdoptionComfort:parseInt(document.getElementById("q-tech-comfort")?.value||"1",10),launchTimelineGoal:document.getElementById("q-launch-timeline")?.value||"",additionalWorkflowNotes:document.getElementById("q-additional")?.value||"",companyProfile:{companyName:document.getElementById("q-company-name")?.value.trim()||"",slogan:document.getElementById("q-slogan")?.value.trim()||"",ownerName:document.getElementById("q-owner-name")?.value.trim()||"",ownerEmail:document.getElementById("q-owner-email")?.value.trim()||"",website:document.getElementById("q-website")?.value.trim()||"",cityState:document.getElementById("q-city")?.value.trim()||"",brandColor:document.getElementById("q-brand-color")?.value||"",logoUrl:document.getElementById("q-logo-url")?.value.trim()||"",logoDataUrl:n||""},submittedAt:new Date().toISOString()}};t||(R.email=document.getElementById("q-owner-email")?.value.trim()||"");try{const k=await fetch(`${O}/intakes/questionnaire/${t||"unknown"}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(R)});if(k.ok){const _=document.querySelector(".form-card");_&&(_.innerHTML=` 1963 <div style="text-align: center; padding: var(--space-8) 0; animation: rl-rise 0.5s ease forwards;"> 1964 <div style="font-size: 4rem; color: #10b981; margin-bottom: var(--space-4);">ð</div> 1965 <h3 style="margin-bottom: var(--space-4); font-size: 1.8rem; font-weight: 800; font-family: var(--font-heading); background: var(--grad-text); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">
1965Profile Completed!</h3> 1966 <p style="color: var(--text-dim); margin-bottom: var(--space-8); font-size: var(--text-base); max-width: 500px; margin-left: auto; margin-right: auto; line-height: var(--leading-normal);"> 1967 Thank you! We've saved your business profile responses. Our team will review this in detail to prepare for your discovery workshop call. 1968 </p> 1969 <button class="btn btn--primary" onclick="window.location.hash='#/home'" style="background: var(--grad-brand);"> 1970 Return Home 1971 </button> 1972 </div> 1973 `)}else{const _=await k.json();throw new Error(_.error||"Server error")}}catch(M){c&&(c.disabled=!1,c.textContent="Submit Profile â"),m&&(m.textContent=`Error submitting form: ${M.message}. Please try again.`,m.className="form-status form-status--error")}})}const ds="modulepreload",cs=function(e){return"/"+e},Ht={},Rt=function(a,t,n){let o=Promise.resolve();if(t&&t.length>0){let i=function(l){return Promise.all(l.map(c=>Promise.resolve(c).then(p=>({status:"fulfilled",value:p}),p=>({status:"rejected",reason:p}))))};document.getElementsByTagName("link");const r=document.querySelector("meta[property=csp-nonce]"),d=r?.nonce||r?.getAttribute("nonce");o=i(t.map(l=>{if(l=cs(l),l in Ht)return;Ht[l]=!0;const c=l.endsWith(".css"),p=c?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${l}"]${p}`))return;const u=document.createElement("link");if(u.rel=c?"stylesheet":ds,c||(u.as="script"),u.crossOrigin="",u.href=l,d&&u.setAttribute("nonce",d),document.head.appendChild(u),c)return new Promise((g,y)=>{u.addEventListener("load",g),u.addEventListener("error",()=>y(new Error(`Unable to preload CSS for ${l}`)))})}))}function s(i){const r=new Event("vite:preloadError",{cancelable:!0});if(r.payload=i,window.dispatchEvent(r),!r.defaultPrevented)throw i}return o.then(i=>{for(const r of i||[])r.status==="rejected"&&s(r.reason);return a().catch(s)})},ps={projectId:"bizwrap-ai-prod",appId:"1:480877542373:web:2923d2fc004a86a6344d0c",storageBucket:"bizwrap-ai-prod.firebasestorage.app",apiKey:"AIzaSyATeaBa8FH_jsWv46pcRVO6bq6VIjd3ghI",authDomain:"bizwrap-ai-prod.firebaseapp.com",messagingSenderId:"480877542373",measurementId:"G-S9093ZJGTF"};let nt=null;function et(){return nt||(nt=(async()=>{const{initializeApp:e}=await Rt(async()=>{const{initializeApp:l}=await import("https://www.gstatic.com/firebasejs/11.8.1/firebase-app.js");return{initializeApp:l}},[]),{getAuth:a,GoogleAuthProvider:t,signInWithPopup:n,signInWithEmailAndPassword:o,signOut:s,onAuthStateChanged:i}=await Rt(async()=>{const{getAuth:l,GoogleAuthProvider:c,signInWithPopup:p,signInWithEmailAndPassword:u,signOut:g,onAuthStateChanged:y}=await import("https://www.gstatic.com/firebasejs/11.8.1/firebase-auth.js");return{getAuth:l,GoogleAuthProvider:c,signInWithPopup:p,signInWithEmailAndPassword:u,signOut:g,onAuthStateChanged:y}},[]),r=e(ps),d=a(r);return i(d,async l=>{if(l)try{localStorage.setItem("bizwrap_token",await l.getIdToken())}catch{}Ve&&(Ve(),Ve=null)}),setInterval(async()=>{const l=d.currentUser;if(l)try{localStorage.setItem("bizwrap_token",await l.getIdToken())}catch{}},1800*1e3),{auth:d,GoogleAuthProvider:t,signInWithPopup:n,signInWithEmailAndPassword:o,signOut:s,onAuthStateChanged:i}})()),nt}let Ve=null;const us=new Promise(e=>{Ve=e});async function jt(e=!1){try{const a=await et();await Promise.race([us,new Promise(o=>setTimeout(o,4e3))]);const t=a.auth.currentUser;if(!t)return localStorage.getItem("bizwrap_token");const n=await t.getIdToken(e);return localStorage.setItem("bizwrap_token",n),n}catch(a){return console.warn("Failed to get valid token from Firebase:",a),localStorage.getItem("bizwrap_token")}}function hs(){return` 1974 <section class="section" style="min-height:80vh;display:flex;align-items:center;justify-content:center;padding:var(--space-12) var(--space-6);"> 1975 <div class="form-card" style="max-width:440px;width:100%;"> 1976 1977 <!-- Logo --> 1978 <div style="text-align:center;margin-bottom:var(--space-8);"> 1979 <img src="/assets/bizwrap-logo-lockup.svg" alt="BizWrap.ai" style="height:48px;margin:0 auto;" /> 1980 <p style="color:var(--text-dim);font-size:var(--text-sm);margin-top:var(--space-3);"> 1981 Sign in to manage your BizWrap dashboard 1982 </p> 1983 </div> 1984 1985 <!-- Google Sign-In --> 1986 <button id="admin-google-btn" type="button" style=" 1987 width:100%; 1988 display:inline-flex; 1989 align-items:center; 1990 justify-content:center; 1991 gap:var(--space-3); 1992 padding:var(--space-3) var(--space-6); 1993 background:#fff; 1994 color:var(--ink); 1995 font-family:var(--font-body); 1996 font-size:var(--text-sm); 1997 font-weight:var(--weight-semibold); 1998 border:1.5px solid var(--border-strong); 1999 border-radius:var(--radius-full); 2000 cursor:pointer; 2001 transition:all var(--transition-base); 2002 "> 2003 <svg width="18" height="18" viewBox="0 0 48 48"> 2004 <path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/> 2005 <path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/> 2006 <path fill="#FBBC05" d="M10.53 28.59a14.5 14.5 0 0 1 0-9.18l-7.98-6.19a24.0 24.0 0 0 0 0 21.56l7.98-6.19z"/> 2007 <path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/> 2008 <path fill="none" d="M0 0h48v48H0z"/> 2009 </svg> 2010 Sign in with Google 2011 </button> 2012 2013 <!-- Divider --> 2014 <div style="display:flex;align-items:center;gap:var(--space-4);margin:var(--space-6) 0;">
2015 <span style="flex:1;height:1px;background:var(--border-strong);"></span> 2016 <span style="font-size:var(--text-xs);font-weight:var(--weight-semibold);color:var(--muted-light);text-transform:uppercase;letter-spacing:0.1em;">or</span> 2017 <span style="flex:1;height:1px;background:var(--border-strong);"></span> 2018 </div> 2019 2020 <!-- Email / Password --> 2021 <div class="form-group"> 2022 <label class="form-label" for="admin-email">Email address</label> 2023 <input class="form-input" type="email" id="admin-email" placeholder="[email protected]" autocomplete="email" /> 2024 </div> 2025 2026 <div class="form-group"> 2027 <label class="form-label" for="admin-pass">Password</label> 2028 <input class="form-input" type="password" id="admin-pass" placeholder="â¢â¢â¢â¢â¢â¢â¢â¢" autocomplete="current-password" /> 2029 </div> 2030 2031 <button id="admin-login-btn" class="btn btn--primary" type="button" style="width:100%;"> 2032 Sign in 2033 </button> 2034 2035 <!-- Status --> 2036 <div id="admin-login-status" class="form-status" style="margin-top:var(--space-4);"></div> 2037 </div> 2038 </section> 2039 `}function ms(){const e=document.getElementById("admin-google-btn"),a=document.getElementById("admin-login-btn"),t=document.getElementById("admin-login-status");e?.addEventListener("click",async()=>{if(o(),!!["localhost","127.0.0.1"].includes(window.location.hostname)){console.log("[AUTH] Local bypass triggered via Google Sign-In"),localStorage.setItem("bizwrap_token","mock-local-token"),localStorage.setItem("bizwrap_user","local-admin"),window.location.hash="#/admin";return}try{const{auth:i,GoogleAuthProvider:r,signInWithPopup:d,signOut:l}=await et(),c=new r,u=(await d(i,c)).user;if(u.email&&u.email.endsWith("@murphychristmaslighting.com")){const g=await u.getIdToken();localStorage.setItem("bizwrap_token",g),localStorage.setItem("bizwrap_user",u.displayName||u.email.split("@")[0]),window.location.hash="#/admin"}else await l(i),n("Access restricted to Murphy Christmas Lighting team members.")}catch(i){n(i.message)}}),a?.addEventListener("click",async()=>{o();const s=document.getElementById("admin-email")?.value?.trim(),i=document.getElementById("admin-pass")?.value;if(!s||!i){n("Please enter your email and password.");return}if(!!["localhost","127.0.0.1"].includes(window.location.hostname)&&(s==="admin@local"||s.endsWith("@murphychristmaslighting.com"))){console.log("[AUTH] Local bypass triggered via Email/Password"),localStorage.setItem("bizwrap_token","mock-local-token"),localStorage.setItem("bizwrap_user",s.split("@")[0]),window.location.hash="#/admin";return}try{const{auth:d,signInWithEmailAndPassword:l}=await et(),c=await l(d,s,i),p=await c.user.getIdToken();localStorage.setItem("bizwrap_token",p),localStorage.setItem("bizwrap_user",c.user.displayName||s.split("@")[0]),window.location.hash="#/admin"}catch(d){n(d.message)}}),document.getElementById("admin-pass")?.addEventListener("keydown",s=>{s.key==="Enter"&&a?.click()});function n(s){t&&(t.textContent=s,t.className="form-status form-status--error")}function o(){t&&(t.textContent="",t.className="form-status")}}var ot=null,re=!1;const it=[{key:"new",label:"New Lead",icon:"ð¥"},{key:"called",label:"Contacted",icon:"ð"},{key:"interested",label:"Interested",icon:"ð¡"},{key:"discovery",label:"Discovery",icon:"ð "},{key:"onboarding",label:"Onboarding",icon:"ð"},{key:"converted",label:"Converted",icon:"â"}];function vs(e){const a=[];if(a.push("completed"),e.called)a.push("completed");else return e.contactAttempted||e.contactFailed,a.push("in-progress"),he(a),a;if(e.interested==="yes")a.push("completed");else return e.interested==="no"?(a.push("declined"),he(a),a):(a.push("in-progress"),he(a),a);if(e.discoveryCall==="completed")a.push("completed");else return e.discoveryCall==="scheduled",a.push("in-progress"),he(a),a;if(e.onboardingStatus==="completed")a.push("completed");else return e.onboardingStatus==="scheduled"||e.onboarding,a.push("in-progress"),he(a),a;return e.status==="converted"?a.push("completed"):a.push("in-progress"),a}function he(e){for(;e.length<6;)e.push("locked")}function fs(e){for(var a="",t=1;t<=5;t++){var n=t<=e?"star-rating__star star-rating__star--filled":"star-rating__star";a+='<span class="'+n+'" data-star="'+t+'">â </span>'}return a}function bt(e){return e.status==="converted"?{label:"Converted",cls:"stage-6"}:(e.onboardingStatus==="completed"||e.onboardingStatus==="scheduled"||e.onboarding)&&e.discoveryCall==="completed"?{label:"Onboarding",cls:"stage-5"}:e.discoveryCall==="completed"&&e.interested==="yes"?{label:"Discovery",cls:"stage-4"}:e.discoveryCall==="scheduled"&&e.interested==="yes"?{label:"Discovery",cls:"stage-4"}:e.interested==="yes"&&e.called?{label:"Interested",cls:"stage-3"}:e.interested==="no"?{label:"Not Interested",cls:"stage-dead"}:e.contactFailed?{label:"Can't Reach",cls:"stage-dead"}:e.called?{label:"Contacted",cls:"stage-2"}:e.contactAttempted?{label:"Not Reached",cls:"stage-1-warn"}:{label:"New Lead",cls:"stage-1"}}var z={};function gs(){return` 2040 <section class="section" style="padding-top:calc(var(--nav-height) + var(--space-8));"> 2041 <div class="container admin-dash"> 2042 <div class="admin-header"> 2043 <h1 class="admin-header__title">Admin Portal</h1> 2044 </div> 2045 <div class="admin-nav">
2046 <span class="admin-nav__link admin-nav__link--active" onclick="window.location.hash='#/admin'">Pipeline</span> 2047 <span class="admin-nav__link" onclick="window.location.hash='#/admin-forms'">Forms Builder</span> 2048 <span class="admin-nav__link" onclick="window.location.hash='#/admin-email'">Email Template</span> 2049 </div> 2050 <div class="admin-kpis" id="admin-kpis"> 2051 <div class="admin-kpi"><div class="admin-kpi__value" id="kpi-total">â</div><div class="admin-kpi__label">Total Leads</div></div> 2052 <div class="admin-kpi"><div class="admin-kpi__value" id="kpi-called">â</div><div class="admin-kpi__label">Called</div></div> 2053 <div class="admin-kpi"><div class="admin-kpi__value" id="kpi-interested">â</div><div class="admin-kpi__label">Interested</div></div> 2054 <div class="admin-kpi"><div class="admin-kpi__value" id="kpi-discovery">â</div><div class="admin-kpi__label">Discovery Scheduled</div></div> 2055 <div class="admin-kpi"><div class="admin-kpi__value" id="kpi-onboarding">â</div><div class="admin-kpi__label">Onboarding</div></div> 2056 </div> 2057 <!-- Schedule Section --> 2058 <div id="pipeline-schedule" class="pipeline-schedule"></div> 2059 2060 <div class="admin-table-wrap" style="border:none;background:transparent;"> 2061 <div class="pipeline-section-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-5);"> 2062 <h3 class="pipeline-section-title" id="pipeline-title">Client Pipeline <span class="pipeline-section-count" id="admin-table-count"></span></h3> 2063 <button class="pipeline-archive-toggle" id="pipeline-archive-toggle" style="background: var(--bg-alt); border: 1px solid var(--border-strong); padding: 8px 16px; border-radius: var(--radius-md); font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--muted); cursor: pointer; display: flex; align-items: center; gap: 8px; transition: var(--transition-fast); font-family: var(--font-body);"> 2064 <span>ðï¸</span> Show Archived Leads 2065 </button> 2066 </div> 2067 <div id="admin-table-body"> 2068 <div class="admin-empty"><div class="admin-empty__icon">ð</div><div class="admin-empty__text">Loading pipelineâ¦</div></div> 2069 </div> 2070 </div> 2071 </div> 2072 </section> 2073 `}function ys(){re=!1,(async()=>{const e=await jt();if(!e){window.location.hash="#/admin-login";return}xe(e)})()}async function xe(e){try{let y=function(){document.getElementById("kanban-modal").style.display="none",document.body.style.overflow="",xe(e)};var r=y;fetch(O+"/calendar/sync-from-google",{method:"POST",headers:{Authorization:"Bearer "+e}}).catch(m=>console.warn("Background calendar sync failed:",m));const d=await fetch(O+"/clients",{headers:{Authorization:"Bearer "+e}});if(!d.ok){if(d.status===401||d.status===403){const m=await jt(!0);if(m&&m!==e)return xe(m);localStorage.removeItem("bizwrap_token"),localStorage.removeItem("bizwrap_user"),window.location.hash="#/admin-login";return}throw new Error(`Server returned status ${d.status}`)}const c=(await d.json()).filter(function(m){return re?m.status==="archived":m.status!=="archived"});document.getElementById("kpi-total").textContent=c.length,document.getElementById("kpi-called").textContent=c.filter(function(m){return m.called}).length,document.getElementById("kpi-interested").textContent=c.filter(function(m){return m.interested==="yes"}).length,document.getElementById("kpi-discovery").textContent=c.filter(function(m){return m.discoveryCall==="scheduled"||m.discoveryCall==="completed"}).length,document.getElementById("kpi-onboarding").textContent=c.filter(function(m){return m.onboarding}).length,document.getElementById("admin-table-count").innerHTML='<span class="pipeline-count-bracket">[</span><span class="pipeline-count-number">'+c.length+'</span><span class="pipeline-count-bracket">]</span>';const p=document.getElementById("pipeline-title"),u=document.getElementById("pipeline-archive-toggle");re?(p&&(p.innerHTML='Archived Leads <span class="pipeline-section-count" id="admin-table-count"></span>'),u&&(u.innerHTML="<span>ð</span> Show Active Pipeline")):(p&&(p.innerHTML='Client Pipeline <span class="pipeline-section-count" id="admin-table-count"></span>'),u&&(u.innerHTML="<span>ðï¸</span> Show Archived Leads")),u&&(u.onclick=function(){re=!re,xe(e)});const g=document.getElementById("pipeline-schedule");if(re?g&&(g.style.display="none"):g&&(g.style.display="block",c.length>0?(ks(c),Ms(c)):g.innerHTML=""),c.length===0){var a=re?"No archived leads. You can archive leads from their details card.":"No leads yet. Submissions from your contact form will appear here.";document.getElementById("admin-table-body").innerHTML='<div class="admin-empty"><div class="admin-empty__icon">ð</div><div class="admin-empty__text">'+a+"</div></div>";return}var t={newLead:{label:"New Lead",color:"#7c3aed",bg:"var(--bg-alt)",icon:"ð¥",clients:[]},contacted:{label:"Contacted",color:"#06b6d4",bg:"var(--bg-alt)",icon:"ð",clients:[]},interested:{label:"Interested",color:"#0d9488",bg:"var(--bg-alt)",icon:"ð¡",clients:[]},discovery:{label:"Discovery",color:"#eab308",bg:"var(--bg-alt)",icon:"ð ",clients:[]}
2073,onboarding:{label:"Onboarding",color:"#f97316",bg:"var(--bg-alt)",icon:"ð",clients:[]},converted:{label:"Converted",color:"#16a34a",bg:"var(--bg-alt)",icon:"â",clients:[]}};c.forEach(function(m){var v=bt(m);v.label==="Converted"?t.converted.clients.push(m):v.label==="Onboarding"?t.onboarding.clients.push(m):v.label==="Discovery"?t.discovery.clients.push(m):v.label==="Interested"?t.interested.clients.push(m):v.label==="Contacted"||v.label==="Not Reached"||v.label==="Not Interested"||v.label==="Can't Reach"?t.contacted.clients.push(m):t.newLead.clients.push(m)});var n=re?"kanban-board kanban-board--archived":"kanban-board",o='<div class="'+n+'">',s=["newLead","contacted","interested","discovery","onboarding","converted"];s.forEach(function(m){var v=t[m],T=m==="newLead"||m==="contacted"||m==="interested";o+='<div class="kanban-column" style="background:'+v.bg+"; border-color:"+v.color+'20;"><div class="kanban-column__header" style="border-top: 3px solid '+v.color+';"><span class="kanban-column__icon">'+v.icon+'</span><span class="kanban-column__title">'+v.label+'</span><span class="kanban-column__count" style="background:'+v.color+';">'+v.clients.length+'</span></div><div class="kanban-column__body">',v.clients.length===0?o+='<div class="kanban-empty">â</div>':v.clients.forEach(function(b){var R=b.businessName||b.name||"Unknown",M="";if(T&&b.createdAt){var k=Math.floor((Date.now()-new Date(b.createdAt).getTime())/6e4);k<60?M=k+"m ago":k<1440?M=Math.floor(k/60)+"h ago":M=Math.floor(k/1440)+"d ago"}for(var _="",I=b.interestRating||0,E=1;E<=5;E++)_+='<span class="kanban-star'+(E<=I?" kanban-star--filled":"")+'">â
</span>';var h=re?"kanban-card kanban-card--archived":"kanban-card",S=b.source==="workshop-waitlist"||/workshop/i.test(b.service||""),x=S?'<span style="display:inline-block;font-size:9px;font-weight:800;letter-spacing:0.04em;text-transform:uppercase;color:#fff;background:linear-gradient(90deg,#7c3aed,#01c7cc);padding:2px 7px;border-radius:999px;margin-left:6px;vertical-align:middle;">ðï¸ Training</span>':"";o+='<div class="'+h+'" data-client-id="'+b.id+'"><div class="kanban-card__row"><div class="kanban-card__info"><span class="kanban-card__name">'+R+x+'</span><div class="kanban-card__stars">'+_+"</div></div>"+(M?'<span class="kanban-card__age">'+M+"</span>":"")+'<span class="kanban-card__arrow">â¸</span></div></div>'}),o+="</div></div>"}),o+="</div>",o+='<div class="kanban-modal" id="kanban-modal" style="display:none;"><div class="kanban-modal__backdrop"></div><div class="kanban-modal__content"><button class="kanban-modal__close">×</button><div class="kanban-modal__body" id="kanban-modal-body"></div></div></div>',document.getElementById("admin-table-body").innerHTML=o;var i={};c.forEach(function(m){i[m.id]=m}),document.querySelectorAll(".kanban-card__row").forEach(function(m){m.addEventListener("click",function(){var v=m.closest(".kanban-card"),T=v.dataset.clientId,b=i[T];if(b){var R=document.getElementById("kanban-modal"),M=document.getElementById("kanban-modal-body");M.innerHTML=Ut(b);var k=M.querySelector(".pipeline-card");k&&k.classList.remove("pipeline-card--collapsed"),R.style.display="flex",document.body.style.overflow="hidden",ut(e)}})}),document.querySelector(".kanban-modal__backdrop").addEventListener("click",y),document.querySelector(".kanban-modal__close").addEventListener("click",y),document.addEventListener("keydown",function(m){m.key==="Escape"&&y()}),ut(e)}catch{document.getElementById("admin-table-body").innerHTML='<div class="admin-empty"><div class="admin-empty__icon">â ï¸</div><div class="admin-empty__text">Failed to load pipeline. Is the server running?</div></div>'}}function be(e){var a=[];if(e.createdAt&&a.push({icon:"ð¥",label:"Lead submitted",by:e.source||"form",at:e.createdAt}),e.calledAt&&a.push({icon:"ð",label:e.called?"Reached":e.contactFailed?"Can't reach":"Not reached",by:e.calledBy,at:e.calledAt}),e.contactLog&&e.contactLog.length&&e.contactLog.forEach(function(n,o){a.push({icon:"ð",label:n.note,by:n.by,at:n.at,field:"contactLog",entry:n})}
2073),e.interestedAt&&a.push({icon:"ð¡",label:"Interest: "+(e.interested||"?")+(e.interestRating?" ("+e.interestRating+"â )":""),by:e.interestedBy,at:e.interestedAt}),e.discoveryAt&&a.push({icon:"ð ",label:"Discovery: "+(e.discoveryCall||"â"),by:e.discoveryBy,at:e.discoveryAt}),e.discoveryLog&&e.discoveryLog.length&&e.discoveryLog.forEach(function(n){a.push({icon:"ð",label:n.note,by:n.by,at:n.at,field:"discoveryLog",entry:n})}),e.onboardingAt&&a.push({icon:"ð",label:"Onboarding: "+(e.onboardingStatus||"started"),by:e.onboardingBy,at:e.onboardingAt}),e.onboardingLog&&e.onboardingLog.length&&e.onboardingLog.forEach(function(n){a.push({icon:"ð",label:n.note,by:n.by,at:n.at,field:"onboardingLog",entry:n})}),e.quickNotes&&e.quickNotes.length&&e.quickNotes.forEach(function(n){a.push({icon:"ð",label:n.note,by:n.by,at:n.at,field:"quickNotes",entry:n})}),e.status==="converted"&&a.push({icon:"â",label:"Converted!",by:"",at:e.updatedAt||e.onboardingAt||""}),a.sort(function(n,o){return new Date(o.at||0)-new Date(n.at||0)}),a.length===0)return'<div class="timeline-empty">No activity yet</div>';var t="";return a.forEach(function(n){var o=new Date(n.at),s=isNaN(o)?"":o.toLocaleDateString("en-US",{month:"short",day:"numeric"})+", "+o.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"}),i=n.field?'<span class="timeline-event__edit" data-field="'+n.field+`" data-entry='`+JSON.stringify(n.entry).replace(/'/g,"'")+"'>âï¸</span>":"";t+='<div class="timeline-event"><div class="timeline-event__dot"></div><div class="timeline-event__content"><div class="timeline-event__header"><span class="timeline-event__icon">'+n.icon+'</span><span class="timeline-event__label">'+n.label+"</span>"+i+'</div><div class="timeline-event__meta">'+(n.by?"ð¤ "+n.by+" · ":"")+s+"</div></div></div>"}),t}const J=e=>String(e??"").replace(/[&<>"']/g,a=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[a]),me=e=>'"'+String(e??"").replace(/"/g,"")+'"',bs=e=>String(e||"").toLowerCase().replace(/[^a-z0-9]+/g,"").slice(0,24)||"tenant",Wt=e=>String(e||"").trim().replace(/^https?:\/\//i,"").replace(/\/.*$/,""),ws="c:\\Users\\murph\\OneDrive\\Desktop\\Thrive\\Crystal";function Es(e,a){const t=["node scripts/make-tenant.mjs","--name "+me(e.companyName)];e.slogan&&t.push("--slogan "+me(e.slogan)),e.ownerName&&t.push("--owner "+me(e.ownerName)),e.ownerEmail&&t.push("--email "+me(e.ownerEmail));const n=Wt(e.website);return n&&t.push("--domain "+me(n)),e.cityState&&t.push("--city "+me(e.cityState)),e.brandColor&&t.push("--color "+me(e.brandColor)),a&&t.push("--logo "+me(a)),t.push("--launch"),t.join(" ")}typeof window<"u"&&(window.cdbCopy=function(e){try{navigator.clipboard.writeText(e.getAttribute("data-cmd")),e.textContent="Copied â",setTimeout(function(){e.textContent="Copy command"},1500)}catch{}},window.cdbLauncher=function(e){var a=e.getAttribute("data-cmd"),t=e.getAttribute("data-path"),n=e.getAttribute("data-slug"),o=`@echo off\r 2074title Crystal Demo - `+n+`\r 2075cd /d "`+t+`"\r 2076`+a+`\r 2077pause\r 2078`,s=new Blob([o],{type:"application/octet-stream"}),i=document.createElement("a");i.href=URL.createObjectURL(s),i.download="demo-"+n+".bat",document.body.appendChild(i),i.click(),i.remove(),setTimeout(function(){URL.revokeObjectURL(i.href)},2e3)});function _s(e){const a=e&&e.questionnaire&&e.questionnaire.companyProfile;if(!a||!a.companyName)return"";const t=location.hostname==="localhost"||location.hostname==="127.0.0.1"?"http://localhost:5000":location.origin,n=a.logoUrl||(a.logoDataUrl?t+"/api/intakes/"+e.id+"/logo":""),o=bs(a.companyName),s=Es(a,n),i=a.logoDataUrl||a.logoUrl,r="font-size:10px;font-weight:700;text-transform:uppercase;color:var(--muted-light);letter-spacing:0.05em;";var d='<div style="border:1px solid var(--brand,#7c3aed);border-radius:var(--radius-md,10px);padding:var(--space-4);margin-bottom:var(--space-4);background:var(--bg-alt);">';return d+='<div style="font-weight:800;font-size:14px;margin-bottom:var(--space-3);">ð Crystal Demo Builder</div>',d+='<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3);font-size:13px;margin-bottom:var(--space-3);">',d+='<div><div style="'+r+'">Company</div><div style="font-weight:600;">'+J(a.companyName)+"</div></div>",d+='<div><div style="'+r+'">Owner</div><div style="font-weight:600;">'+(J(a.ownerName)||"â")+"</div></div>",d+='<div><div style="'+r+'">Owner email</div><div style="font-weight:600;">'+(J(a.ownerEmail)||"â")+"</div></div>",d+='<div><div style="'+r+'">Website</div><div style="font-weight:600;">'+(J(Wt(a.website))||"â")+"</div></div>",d+='<div><div style="'+r+'">City</div><div style="font-weight:600;">'+(J(a.cityState)||"â")+"</div></div>",d+='<div><div style="'+r+'">Brand color</div><div style="font-weight:600;"><span style="display:inline-block;width:12px;height:12px;border-radius:3px;vertical-align:middle;margin-right:5px;background:'+(J(a.brandColor)||"#7c3aed")+';"></span>'+(J(a.brandColor)||"â")+"</div></div>",d+="</div>",i&&(d+='<div style="margin-bottom:var(--space-3);"><div style="'+r+'">Logo</div><img src="'+J(i)+'" alt="logo" style="max-height:54px;
2078max-width:200px;background:#fff;border-radius:8px;padding:6px;margin-top:4px;"/></div>'),d+='<div style="'+r+'margin-bottom:6px;">Build & open the demo</div>',d+='<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:8px;">',d+='<button type="button" data-cmd="'+J(s)+'" data-path="'+J(ws)+'" data-slug="'+J(o)+'" onclick="cdbLauncher(this)" style="background:var(--brand,#7c3aed);color:#fff;border:none;border-radius:8px;padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;">⬠Download launcher (.bat)</button>',d+='<button type="button" data-cmd="'+J(s)+'" onclick="cdbCopy(this)" style="background:transparent;color:var(--ink);border:1px solid var(--border);border-radius:8px;padding:9px 14px;font-size:12px;font-weight:600;cursor:pointer;">Copy command</button>',d+="</div>",d+='<div style="font-size:12px;color:var(--muted);line-height:1.5;">Click <strong>Download launcher</strong>, then double-click the downloaded <code>demo-'+J(o)+".bat</code> â it builds the demo for "+J(a.companyName)+" (logo and all) and opens it. No typing, no terminal.</div>",d+="</div>",d}function xs(e){const a=e.questionnaire;if(!a)return`<div style="text-align: center; padding: var(--space-8) var(--space-4); color: var(--muted);"><div style="font-size: 2.5rem; margin-bottom: var(--space-3);">ð</div><h4 style="font-weight: 700; color: var(--ink); margin-bottom: 4px;">No business profile submitted yet</h4><p style="font-size: 13px; max-width: 280px; margin: 0 auto; color: var(--muted-light);">The lead hasn't completed the 20-question discovery questionnaire yet.</p></div>`;const t=[{label:"Years in Business",val:a.yearsInBusiness},{label:"Industry / Niche",val:a.industryNiche},{label:"Annual Revenue Range",val:a.revenueRange?a.revenueRange.replace("-"," - ").toUpperCase():"â"},{label:"12-24 Month Revenue Goal",val:a.revenueGoal},{label:"Full-time Office Staff",val:a.officeStaffCount},{label:"Peak Season Crews / Techs",val:a.fieldCrewCount},{label:"Current CRM / Field Software",val:a.currentCrm},{label:"Sales / Design Tools",val:a.designTool},{label:"Accounting / Financials",val:a.accountingTool},{label:"Biggest CRM/Software Frustration",val:a.techFrustrations,fullWidth:!0},{label:"Biggest Operational Bottleneck",val:a.biggestBottleneck,fullWidth:!0},{label:"Estimated Weekly Office Copy-Paste Time",val:a.timeWasteCopyPaste?a.timeWasteCopyPaste.replace("-"," - ")+" hours":"â"},{label:"Field Crew / Job Progress Tracking",val:a.crewTrackingMode},{label:"Estimated Leads Lost / Slip Percentage",val:a.leadSlipPercentage?a.leadSlipPercentage.replace("-"," - ")+"%":"â"},{label:"Client Comms Setup (Centralized?)",val:a.clientCommsCentralized},{label:"Main Outcome Wanted from Custom OS",val:a.osGoalOutcome,fullWidth:!0},{label:"Desired AI Agent Role / Tasks",val:a.desiredAgentTask,fullWidth:!0},{label:"Team Tech Adoption Comfort (1-10)",val:a.techAdoptionComfort?a.techAdoptionComfort+" / 10":"â"},{label:"Target Launch Timeline",val:a.launchTimelineGoal},{label:"Additional Workflow Notes",val:a.additionalWorkflowNotes,fullWidth:!0}];var n=_s(e);return n+='<div style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); font-size: 13px;">',t.forEach(function(o){const s=o.fullWidth?"grid-column: span 2;":"",i=o.val||"â";n+='<div style="background: var(--bg-alt); padding: var(--space-3) var(--space-4); border-radius: var(--radius-sm); border: 1px solid var(--border); '+s+'"><div style="font-size: 10px; font-weight: 700; text-transform: uppercase; color: var(--muted-light); letter-spacing: 0.05em; margin-bottom: var(--space-1);">'+o.label+'</div><div style="font-weight: 600; color: var(--ink); line-height: 1.4; white-space: pre-wrap;">'+i+"</div></div>"}),n+="</div>",n}function Ut(e){z[e.id]=Object.assign({},e);var a=vs(e),t=e.createdAt?new Date(e.createdAt).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}):"â",n=e.nextCallScheduled||"";n&&n.includes("T")&&(n=n.slice(0,16));var o="";if(e.nextCallScheduled){var s=new Date(e.nextCallScheduled);o="Call on "+s.toLocaleDateString("en-US",{weekday:"short",month:"short",day:"numeric",year:"numeric"})+" at "+s.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"})}for(var i="",r=0;r<it.length;r++){var C=it[r],d=a[r],l=C.icon;if(i+='<div class="pipeline-progress__step"><div class="pipeline-progress__dot pipeline-progress__dot--'+d+'">'+l+'</div><span class="pipeline-progress__label pipeline-progress__label--'+d+'">'+C.label+"</span></div>",r<it.length-1){var c=d==="completed"?"done":"pending",p=d==="in-progress"?" pipeline-progress__arrow--next":"";i+='<div class="pipeline-progress__arrow pipeline-progress__arrow--'+c+p+'">â</div>'}}var u=e.discoveryCall==="not-scheduled"||!e.discoveryCall?" active":"",g=e.discoveryCall==="scheduled"?" active":"",y=e.discoveryCall==="completed"?" active":"",m=e.interested!=="yes"?" disabled":"";e.discoveryCall==="scheduled"&&""+e.id+n+e.id+(o?'<span class="pipeline-date__preview">'+o+"</span>":"");var v="";e.discoveryCall==="completed"&&(v='<div class="pipeline-step__expand"><div class="contact-memo discovery-notes" data-id="'+e.id+'" data-log-field="discoveryLog"><div class="contact-memo__custom"><textarea class="contact-memo__text" placeholder="Add notes about the discovery callâ¦"></textarea><button class="contact-memo__save contact-memo__save--discovery" data-id="'+e.id+'">Save Note</button></div>'+(Array.isArray(e.discoveryLog)&&e.discoveryLog.length>0?'<div class="contact-log">'+e.discoveryLog.map(function(f){var L=new Date(f.at),P=isNaN(L)?"":L.toLocaleDateString("en-US",{month:"short",day:"numeric"})+", "+L.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"});return'<div class="contact-log__entry"><span class="contact-log__meta">
2078ð¤ '+(f.by||"Unknown")+" · "+P+'</span><span class="contact-log__text">'+f.note+"</span></div>"}).reverse().join("")+"</div>":"")+"</div></div>");var T=e.onboardingStatus==="not-scheduled"||!e.onboardingStatus?" active":"",b=e.onboardingStatus==="scheduled"?" active":"",R=e.onboardingStatus==="completed"?" active":"",M=e.discoveryCall!=="completed"?" disabled":"",k=e.onboardingScheduled||"";if(k&&k.includes("T")&&(k=k.slice(0,16)),e.onboardingScheduled){var _=new Date(e.onboardingScheduled);""+_.toLocaleDateString("en-US",{weekday:"short",month:"short",day:"numeric",year:"numeric"})+_.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"})}var I=e.interested==="unknown"?" active":"",E=e.interested==="yes"?" active":"",h=e.interested==="no"?" active":"",S=e.called?"":" disabled",x=e.interestRating||0,C=bt(e),H=C.label,w=C.cls;return e.message&&(e.message.length>120?e.message.substring(0,120)+"":e.message),'<div class="pipeline-card pipeline-card--collapsed" data-id="'+e.id+'"><div class="pipeline-card__header pipeline-card__toggle"><div class="pipeline-card__lead"><h4 class="pipeline-card__name">'+(e.name||"â")+'</h4><span class="pipeline-card__company">'+(e.company||"â")+'</span></div><div class="pipeline-card__header-right"><span class="pipeline-card__status pipeline-card__status--'+w+'">'+H+"</span>"+(x>0?'<span class="pipeline-card__stars-mini">'+"â
".repeat(x)+"â".repeat(5-x)+"</span>":"")+'<span class="pipeline-card__chevron">â¾</span></div></div><div class="pipeline-card__info-bar"><div class="info-bar__item"><span class="info-bar__icon">ð§</span>'+(e.email?'<a href="https://mail.google.com/mail/?view=cm&to='+encodeURIComponent(e.email)+"&su="+encodeURIComponent("BizWrap - "+(e.businessName||e.name||""))+'" target="_blank" class="pipeline-card__email-link">'+e.email+"</a>":'<span class="info-bar__empty">â</span>')+'</div><div class="info-bar__item"><span class="info-bar__icon">ð±</span>'+(e.phone?'<a href="tel:'+e.phone+'" class="pipeline-card__phone-link">'+e.phone+"</a>":'<span class="info-bar__empty">â</span>')+'</div><div class="info-bar__item"><span class="info-bar__icon">ð·ï¸</span><span>'+(e.service||"â")+'</span></div><div class="info-bar__item"><span class="info-bar__icon">ð
</span><span>'+t+"</span></div>"+(e.source?'<div class="info-bar__item"><span class="info-bar__icon">ð</span><span class="admin-source">'+e.source+"</span></div>":"")+'</div><div class="pipeline-progress">'+i+'</div><div class="pipeline-card__body"><div class="pipeline-card__split"><div class="pipeline-card__left">'+(e.message?'<div class="pipeline-card__message" style="margin-bottom: var(--space-4); background: #fffbeb; border: 1px solid #fde047; padding: var(--space-4); border-radius: var(--radius-md);"><div style="display: flex; align-items: center; gap: 6px; font-weight: 700; color: #854d0e; font-size: 13px; margin-bottom: var(--space-1);"><span>ð¬</span> Lead Message</div><div style="font-size: 13px; color: var(--ink); line-height: 1.5; white-space: pre-wrap;">'+e.message+"</div></div>":"")+'<div class="pipeline-card__stages-glass"><div class="pipeline-step"><div class="pipeline-step__row" style="display: flex; justify-content: space-between; align-items: center;"><div class="pipeline-step__info" style="display: flex; align-items: center; gap: 8px;"><span class="pipeline-step__icon">ð</span><span class="pipeline-step__title" style="font-weight: 700; color: var(--ink);">Initial Contact</span></div><div class="segmented-control contact-result" data-id="'+e.id+'"><button class="segmented-btn segmented-btn--red'+(e.contactFailed?" active":"")+'" data-val="cant-reach">Failed</button><button class="segmented-btn segmented-btn--warn'+(e.contactAttempted&&!e.called&&!e.contactFailed?" active":"")+'" data-val="not-reached">Missed</button><button class="segmented-btn segmented-btn--green'+(e.called?" active":"")+'" data-val="reached">Reached</button></div></div><div class="pipeline-step__expand"><div class="contact-memo" data-id="'+e.id+'"><div class="contact-memo__custom"><textarea class="contact-memo__text" placeholder="Add a note about initial contactâ¦"></textarea><button class="contact-memo__save" data-id="'+e.id+'">Save Note</button></div></div></div>'+(e.called&&Array.isArray(e.contactLog)&&e.contactLog.length>0?'<div class="contact-log contact-log--history">'+e.contactLog.map(function(f){var L=new Date(f.at),P=isNaN(L)?"":L.toLocaleDateString("en-US",{month:"short",day:"numeric"})+", "+L.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"});return'<div class="contact-log__entry"><span class="contact-log__meta">
2078ð¤ '+(f.by||"Unknown")+" · "+P+'</span><span class="contact-log__text">'+f.note+"</span></div>"}).reverse().join("")+"</div>":"")+'</div><div class="pipeline-step '+(e.called?"":"pipeline-step--locked")+'"><div class="pipeline-step__row" style="display: flex; justify-content: space-between; align-items: center;"><div class="pipeline-step__info" style="display: flex; align-items: center; gap: 8px;"><span class="pipeline-step__icon">ð¡</span><span class="pipeline-step__title" style="font-weight: 700; color: var(--ink);">Interest Level</span>'+(e.called?'<div class="star-rating" data-field="interestRating" data-id="'+e.id+'" style="margin-left: 8px;">'+fs(x)+"</div>":"")+'</div><div class="segmented-control admin-tri" data-field="interested" data-id="'+e.id+'"><button class="segmented-btn segmented-btn--gray'+I+'" data-val="unknown"'+S+'>?</button><button class="segmented-btn segmented-btn--red'+h+'" data-val="no"'+S+'>No</button><button class="segmented-btn segmented-btn--green'+E+'" data-val="yes"'+S+'>Yes</button></div></div></div><div class="pipeline-step '+(e.interested!=="yes"?"pipeline-step--locked":"")+'"><div class="pipeline-step__row" style="display: flex; justify-content: space-between; align-items: center;"><div class="pipeline-step__info" style="display: flex; align-items: center; gap: var(--space-3);"><div style="display: flex; align-items: center; gap: 8px;"><span class="pipeline-step__icon">ð
</span><span class="pipeline-step__title" style="font-weight: 700; color: var(--ink);">Discovery Call</span></div><select class="admin-select admin-select--assignee" data-field="discoveryAssignee" data-id="'+e.id+'"'+(e.interested!=="yes"?" disabled":"")+'><option value=""'+(e.discoveryAssignee?"":" selected")+'>Assign toâ¦</option><option value="Joey"'+(e.discoveryAssignee==="Joey"?" selected":"")+'>Joey</option><option value="Chelsea"'+(e.discoveryAssignee==="Chelsea"?" selected":"")+'>Chelsea</option><option value="Gracie"'+(e.discoveryAssignee==="Gracie"?" selected":"")+'>Gracie</option></select></div><div class="segmented-control admin-tri" data-field="discoveryCall" data-id="'+e.id+'"><button class="segmented-btn segmented-btn--gray'+u+'" data-val="not-scheduled"'+m+'>None</button><button class="segmented-btn segmented-btn--warn'+g+'" data-val="scheduled"'+m+'>Sched</button><button class="segmented-btn segmented-btn--green'+y+'" data-val="completed"'+m+">Done</button></div></div>"+(e.discoveryCall==="scheduled"?'<div class="pipeline-step__date-row"><input type="datetime-local" class="pipeline-date__input pipeline-date__input--inline" data-field="nextCallScheduled" data-id="'+e.id+'" value="'+n+'" /><button class="pipeline-date__save pipeline-date__save--inline'+(e.nextCallScheduled?" pipeline-date__save--saved":"")+'" data-field="nextCallScheduled" data-id="'+e.id+'">'+(e.nextCallScheduled?"â Saved":"Save")+'</button><button class="pipeline-date__crystal" data-id="'+e.id+'" data-title="Discovery Call" title="Sync to Crystal Calendar">'+(e.crystalEventId?"â
":"ð
")+'</button></div><div class="pipeline-step__meet-row"><span style="font-size: 1.1rem;" title="Google Meet Link">ð¥</span><div class="pipeline-meet__view" style="display: '+(e.discoveryMeetLink?"flex":"none")+';"><a href="'+(e.discoveryMeetLink||"#")+'" target="_blank" class="pipeline-meet__link">Join Google Meet</a><button class="pipeline-meet__edit" data-id="'+e.id+'" title="Edit Google Meet Link">âï¸</button></div><div class="pipeline-meet__edit-container" style="display: '+(e.discoveryMeetLink?"none":"flex")+';"><input type="text" class="pipeline-meet__input" data-field="discoveryMeetLink" data-id="'+e.id+'" placeholder="Paste Google Meet Link..." value="'+(e.discoveryMeetLink||"")+'" /><button class="pipeline-meet__save" data-field="discoveryMeetLink" data-id="'+e.id+'">Save Link</button></div></div>':"")+v+'</div><div class="pipeline-step '+(e.discoveryCall!=="completed"?"pipeline-step--locked":"")+'"><div class="pipeline-step__row" style="display: flex; justify-content: space-between; align-items: center;"><div class="pipeline-step__info" style="display: flex; align-items: center; gap: var(--space-3);"><div style="display: flex; align-items: center; gap: 8px;"><span class="pipeline-step__icon">ð</span><span class="pipeline-step__title" style="font-weight: 700; color: var(--ink);">Onboarding</span></div><select class="admin-select admin-select--assignee" data-field="onboardingAssignee" data-id="'+e.id+'"'+(e.discoveryCall!=="completed"?" disabled":"")+'><option value=""'+(e.onboardingAssignee?"":" selected")+'>Assign toâ¦</option><option value="Joey"'+(e.onboardingAssignee==="Joey"?" selected":"")+'>Joey</option><option value="Chelsea"'+(e.onboardingAssignee==="Chelsea"?" selected":"")+'>Chelsea</option><option value="Gracie"'+(e.onboardingAssignee==="Gracie"?" selected":"")+'>Gracie</option></select></div><div class="segmented-control admin-tri" data-field="onboardingStatus" data-id="'+e.id+'"><button class="segmented-btn segmented-btn--gray'+T+'" data-val="not-scheduled"'+M+'>None</button><button class="segmented-btn segmented-btn--warn'+b+'" data-val="scheduled"'+M+'>Sched</button><button class="segmented-btn segmented-btn--green'+R+'" data-val="completed"'+M+">Done</button></div></div>"+(e.onboardingStatus==="scheduled"?'<div class="pipeline-step__date-row"><input type="datetime-local" class="pipeline-date__input pipeline-date__input--inline" data-field="onboardingScheduled" data-id="'+e.id+'" value="'+k+'" /><button class="pipeline-date__save pipeline-date__save--inline'+(e.onboardingScheduled?" pipeline-date__save--saved":"")+'" data-field="onboardingScheduled" data-id="'+e.id+'">'+(e.onboardingScheduled?"â Saved":"Save")+'</button><button class="pipeline-date__crystal" data-id="'+e.id+'" data-title="Onboarding" title="Sync to Crystal Calendar">'+(e.crystalEventId?"â
":"ð
")+"</button></div>":"")+'<div class="pipeline-step__expand"><div class="contact-memo" data-id="'+e.id+'" data-log-field="onboardingLog"><div class="contact-memo__custom"><textarea class="contact-memo__text" placeholder="Add a note about onboardingâ¦"></textarea><button class="contact-memo__save" data-id="'+e.id+'">Save Note</button></div></div></div></div><div class="pipeline-step '+(e.onboardingStatus!=="completed"?"pipeline-step--locked":"")+'"><div class="pipeline-step__row" style="display: flex; justify-content: space-between; align-items: center;"><div class="pipeline-step__info" style="display: flex; align-items: center; gap: 8px;"><span class="pipeline-step__icon">â</span><span class="pipeline-step__title" style="font-weight: 700; color: var(--ink);">Converted</span></div><label class="admin-toggle '+(e.status==="converted"?"admin-toggle--on":"")+'" data-field="status" data-id="'+e.id+'" data-val-on="converted" data-val-off="new"><span class="admin-toggle__track"></span></label></div>'+(e.status==="converted"?'<div class="pipeline-step__expand"><div class="build-checklist" data-id="'+e.id+'"><div class="build-checklist__item"><label class="build-checklist__label"><input type="checkbox" class="build-checklist__check" data-field="buildGrounding"'+(e.buildGrounding?" checked":"")+' /> Grounding Data</label></div><div class="build-checklist__item"><label class="build-checklist__label"><input type="checkbox" class="build-checklist__check" data-field="buildFlows"'+(e.buildFlows?" checked":"")+' /> Application Flows</label></div><div class="build-checklist__item"><label class="build-checklist__label"><input type="checkbox" class="build-checklist__check" data-field="buildAgents"'+(e.buildAgents?" checked":"")+' /> Building Agents</label></div><div class="build-checklist__item"><label class="build-checklist__label"><input type="checkbox" class="build-checklist__check" data-field="buildInterface"'+(e.buildInterface?" checked":"")+' /> Building Interface</label></div><div class="build-checklist__item"><label class="build-checklist__label"><input type="checkbox" class="build-checklist__check" data-field="buildComplete"'+(e.buildComplete?" checked":"")+" /> Initial Builds Complete</label></div></div></div>":"")+'</div></div><div class="pipeline-card__notes"><div class="pipeline-card__notes-header"><span class="pipeline-step__icon">ð</span><span class="pipeline-step__title">Quick Note</span></div><div class="pipeline-card__notes-input"><textarea class="pipeline-note__text" data-id="'+e.id+'" placeholder="Add a note about this leadâ¦"></textarea><button class="pipeline-note__save" data-id="'+e.id+'">Save</button></div></div><div class="pipeline-card__actions">'+(e.status==="archived"?'<button class="pipeline-action-btn pipeline-action-btn--archive" data-id="'+e.id+'" data-action="restore"><span>ð¤</span> Restore Lead</button>':'<button class="pipeline-action-btn pipeline-action-btn--archive" data-id="'+e.id+'" data-action="archive"><span>ðï¸</span> Archive Lead</button>')+'<button class="pipeline-action-btn pipeline-action-btn--delete" data-id="'+e.id+'"><span>ðï¸</span> Delete Lead</button></div></div><div class="pipeline-card__right"><div class="timeline-panel"><div class="timeline-panel__tabs"><button class="timeline-tab timeline-tab--active" data-tab="timeline" data-id="'+e.id+'"><span>ð</span> Timeline</button><button class="timeline-tab" data-tab="profile" data-id="'+e.id+'"><span>ð</span> Business Profile'+(e.questionnaire?'<span style="background: var(--success); color: #fff; font-size: 9px; padding: 2px 8px; border-radius: var(--radius-full); font-weight: var(--weight-extrabold); letter-spacing: 0.05em; display: inline-block; margin-left: 2px;">DONE</span>':"")+'</button></div><div class="timeline-scroll-container tab-content--timeline" id="tab-timeline-'+e.id+'" style="flex: 1; overflow-y: auto; padding: var(--space-4) var(--space-5);"><div class="timeline-panel__body">'+be(e)+'</div></div><div class="profile-scroll-container tab-content--profile" id="tab-profile-'+e.id+'" style="display: none; flex: 1; overflow-y: auto; padding: var(--space-5);">'+xs(e)+"</div></div></div></div></div></div>"}function oe(e,a){var t=e.id;z[t]=Object.assign({},e);var n=document.getElementById("kanban-modal");if(n&&n.style.display==="flex"){var o=document.getElementById("kanban-modal-body");if(o){o.innerHTML=Ut(e);var s=o.querySelector(".pipeline-card");s&&s.classList.remove("pipeline-card--collapsed"),ut(a)}}}function ut(e){var a=localStorage.getItem("bizwrap_user")||"Unknown";document.querySelectorAll(".pipeline-card__toggle").forEach(function(n){n.addEventListener("click",function(o){if
2078(!(o.target.closest("button")||o.target.closest("select")||o.target.closest("input"))){var s=n.closest(".pipeline-card");s&&s.classList.toggle("pipeline-card--collapsed")}})}),document.querySelectorAll(".pipeline-note__save").forEach(function(n){n.addEventListener("click",async function(){var o=n.dataset.id,s=n.closest(".pipeline-card__notes-input").querySelector(".pipeline-note__text");if(!(!s||!s.value.trim())){var i=s.value.trim(),r={note:i,by:a,at:new Date().toISOString()};if(await W(o,{quickNotesEntry:r},e),s.value="",z[o]){z[o].quickNotes||(z[o].quickNotes=[]),z[o].quickNotes.push(r);var d=n.closest(".pipeline-card");if(d){var l=d.querySelector(".timeline-panel__body");l&&(l.innerHTML=be(z[o]))}}n.textContent="â Saved!",n.classList.add("pipeline-note__save--saved"),setTimeout(function(){n.textContent="Save",n.classList.remove("pipeline-note__save--saved")},2e3)}})}),document.querySelectorAll(".timeline-panel__body").forEach(function(n){n.addEventListener("click",function(o){var s=o.target.closest(".timeline-event__edit");if(s){o.stopPropagation(),document.querySelectorAll(".timeline-edit-pop").forEach(function(u){u.remove()});var i=s.closest(".timeline-event"),r=s.dataset.field,d=JSON.parse(s.dataset.entry),l=s.closest(".pipeline-card"),c=l?l.dataset.id:null;if(c){var p=document.createElement("div");p.className="timeline-edit-pop",p.innerHTML='<button class="timeline-edit-pop__btn timeline-edit-pop__btn--edit">âï¸ Edit</button><button class="timeline-edit-pop__btn timeline-edit-pop__btn--delete">ðï¸ Delete</button>',i.appendChild(p),p.querySelector(".timeline-edit-pop__btn--delete").addEventListener("click",async function(){await W(c,{deleteLogEntry:{field:r,entry:d}},e),z[c]&&z[c][r]&&(z[c][r]=z[c][r].filter(function(g){return g.at!==d.at||g.note!==d.note}));var u=l.querySelector(".timeline-panel__body");u&&(u.innerHTML=be(z[c]))}),p.querySelector(".timeline-edit-pop__btn--edit").addEventListener("click",function(){p.remove();var u=i.querySelector(".timeline-event__label"),g=u.textContent;u.innerHTML='<input class="timeline-edit-input" value="'+g.replace(/"/g,""")+'" /><button class="timeline-edit-save">Save</button>';var y=u.querySelector(".timeline-edit-input");y.focus(),y.select(),u.querySelector(".timeline-edit-save").addEventListener("click",async function(){var m=y.value.trim();if(!m||m===g){u.textContent=g;return}var v={note:m,by:d.by,at:d.at};await W(c,{deleteLogEntry:{field:r,entry:d}},e);var T=r+"Entry";await W(c,{[T]:v},e),z[c]&&z[c][r]&&(z[c][r]=z[c][r].map(function(R){return R.at===d.at&&R.note===d.note?v:R}));var b=l.querySelector(".timeline-panel__body");b&&(b.innerHTML=be(z[c]))})}),setTimeout(function(){document.addEventListener("click",function u(g){p.contains(g.target)||(p.remove(),document.removeEventListener("click",u))})},0)}}})}),document.querySelectorAll(".contact-result").forEach(function(n){var o=n.dataset.id;n.querySelectorAll(".segmented-btn").forEach(function(s){s.addEventListener("click",async function(){var i=s.dataset.val,r=new Date().toISOString(),d={calledBy:a,calledAt:r};i==="reached"?(d.called=!0,d.contactAttempted=!1,d.contactFailed=!1):i==="not-reached"?(d.called=!1,d.contactAttempted=!0,d.contactFailed=!1):i==="cant-reach"&&(d.called=!1,d.contactAttempted=!1,d.contactFailed=!0),n.querySelectorAll(".segmented-btn").forEach(function(g){g.classList.remove("active")}),s.classList.add("active");var c=n.closest(".pipeline-card");if(c&&z[o]){Object.assign(z[o],d),z[o].calledBy=a,z[o].calledAt=r;var l=c.querySelector(".timeline-panel__body");l&&(l.innerHTML=be(z[o]))}var c=n.closest(".pipeline-card");if(c){var p=c.querySelector(".pipeline-card__status");p&&(p.className="pipeline-card__status",i==="reached"?(p.textContent="Contacted",p.classList.add("pipeline-card__status--stage-2")):i==="not-reached"?(p.textContent="Not Reached",p.classList.add("pipeline-card__status--stage-1-warn")):i==="cant-reach"&&(p.textContent="Can't Reach",p.classList.add("pipeline-card__status--stage-dead")))}var u=await W(o,d,e);u&&oe(u,e)})})});async function t(n,o,s){var i=new Date,r={note:s,by:a,at:i.toISOString()},d=i.toLocaleDateString("en-US",{month:"short",day:"numeric"})+", "+i.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"}),l=n.querySelector(".contact-log");
2078l||(l=document.createElement("div"),l.className="contact-log",n.appendChild(l));var c=document.createElement("div");c.className="contact-log__entry contact-log__entry--new",c.innerHTML='<span class="contact-log__meta">ð¤ '+a+" · "+d+'</span><span class="contact-log__text">'+s+"</span>",l.insertBefore(c,l.firstChild);var p=n.querySelector(".contact-memo__save");p&&(p.textContent="â Saved!",p.classList.add("contact-memo__save--saved"),setTimeout(function(){p.textContent="Save Note",p.classList.remove("contact-memo__save--saved")},2e3));var u=n.dataset.logField||"contactLog",g=u+"Entry",y=await W(o,{[g]:r},e);if(z[o]){z[o][u]||(z[o][u]=[]),z[o][u].push(r);var m=n.closest(".pipeline-card");if(m){var v=m.querySelector(".timeline-panel__body");v&&(v.innerHTML=be(z[o]))}}return y}document.querySelectorAll(".contact-memo").forEach(function(n){var o=n.dataset.id;n.querySelectorAll(".contact-memo__preset").forEach(function(s){s.addEventListener("click",async function(){var i=s.textContent;n.querySelectorAll(".contact-memo__preset").forEach(function(d){d.classList.remove("contact-memo__preset--active")}),s.classList.add("contact-memo__preset--active");var r=n.querySelector(".contact-memo__text");r&&(r.value=""),await t(n,o,i)})})}),document.querySelectorAll(".contact-memo__save").forEach(function(n){n.addEventListener("click",async function(){var o=n.dataset.id,s=n.closest(".contact-memo"),i=s?s.querySelector(".contact-memo__text"):null;if(!(!i||!i.value.trim())){var r=i.value.trim();i.value="",s&&s.querySelectorAll(".contact-memo__preset").forEach(function(d){d.classList.remove("contact-memo__preset--active")}),await t(s,o,r)}})}),document.querySelectorAll(".pipeline-card .admin-tri").forEach(function(n){var o=n.dataset.field,s=n.dataset.id;n.querySelectorAll(".segmented-btn").forEach(function(i){i.addEventListener("click",async function(){if(!i.disabled){var r=i.dataset.val,d=new Date().toISOString();n.querySelectorAll(".segmented-btn").forEach(function(m){m.classList.remove("active")}),i.classList.add("active");var l=n.closest(".pipeline-card"),c=n.dataset.id;if(l&&c&&z[c]){var p=l.querySelector(".timeline-panel__body");p&&(p.innerHTML=be(z[c]))}var u={[o]:r};if(o==="interested")u.interestedBy=a,u.interestedAt=d;else if(o==="discoveryCall"){if(u.discoveryBy=a,u.discoveryAt=d,r==="completed"){var l=n.closest(".pipeline-card");if(l){var g=l.querySelectorAll(".pipeline-step");g.length>0&&g[g.length-1].classList.remove("pipeline-step--locked")}}}else o==="onboardingStatus"&&(u.onboardingBy=a,u.onboardingAt=d,u.onboarding=r==="scheduled"||r==="completed");var y=await W(s,u,e);y&&oe(y,e)}})})}),document.querySelectorAll(".star-rating").forEach(function(n){n.querySelectorAll(".star-rating__star").forEach(function(o){o.addEventListener("click",async function(){var s=n.dataset.id,i=n.dataset.field,r=parseInt(o.dataset.star);n.querySelectorAll(".star-rating__star").forEach(function(l,c){c<r?l.classList.add("star-rating__star--filled"):l.classList.remove("star-rating__star--filled")});var d=await W(s,{[i]:r},e);d&&oe(d,e)})})}),document.querySelectorAll(".pipeline-card .admin-select").forEach(function(n){n.addEventListener("change",async function(){if(!n.disabled){var o=n.dataset.field,s=n.dataset.id,i=await W(s,{[o]:n.value},e);i&&oe(i,e)}})}),document.querySelectorAll('.pipeline-card .admin-toggle[data-field="status"]').forEach(function(n){n.addEventListener("click",async function(){var o=n.closest(".pipeline-step");if(!(o&&o.classList.contains("pipeline-step--locked"))){var s=n.dataset.id,i=n.classList.contains("admin-toggle--on");n.classList.toggle("admin-toggle--on");var r=i?"new":"converted",d=await W(s,{status:r},e);d&&oe(d,e)}})}),document.querySelectorAll(".build-checklist__check").forEach(function(n){n.addEventListener("change",async function(){var o=n.closest(".build-checklist"),s=o?o.dataset.id:null;if(s){var i=n.dataset.field,r=await W(s,{[i]:n.checked},e);r&&oe(r,e)}})}),document.querySelectorAll(".pipeline-date__save").forEach(function(n){n.addEventListener("click",async function(){var o=n.dataset.id,s=n.dataset.field,i=n.closest(".pipeline-date__row"),r=i?i.querySelector(".pipeline-date__input"):n.previousElementSibling;if(!(!r||!r.value)){n.disabled=!0,n.textContent="Savingâ¦";var d=await W(o,{[s]:r.value},e);d&&oe(d,e)}})}),document.querySelectorAll(".pipeline-meet__save").forEach(function(n){n.addEventListener("click",async function(){var o=n.dataset.id,s=n.dataset.f
2078ield,i=n.closest(".pipeline-step__meet-row"),r=i?i.querySelector(".pipeline-meet__input"):null;if(r){n.disabled=!0,n.textContent="Savingâ¦";var d=await W(o,{[s]:r.value.trim()},e);d&&oe(d,e)}})}),document.querySelectorAll(".pipeline-meet__input").forEach(function(n){n.addEventListener("keydown",async function(o){if(o.key==="Enter"){var s=n.dataset.id,i=n.dataset.field,r=n.closest(".pipeline-step__meet-row"),d=r?r.querySelector(".pipeline-meet__save"):null;d&&(d.disabled=!0);var l=await W(s,{[i]:n.value.trim()},e);l&&oe(l,e)}})}),document.querySelectorAll(".pipeline-meet__edit").forEach(function(n){n.addEventListener("click",function(){var o=n.closest(".pipeline-step__meet-row"),s=o?o.querySelector(".pipeline-meet__view"):null,i=o?o.querySelector(".pipeline-meet__edit-container"):null;if(s&&i){s.style.display="none",i.style.display="flex";var r=i.querySelector(".pipeline-meet__input");r&&(r.focus(),r.select())}})}),document.querySelectorAll(".pipeline-date__input").forEach(function(n){n.addEventListener("input",function(){var o=n.closest(".pipeline-step__date-row, .pipeline-step__date-inline, .pipeline-date__row"),s=o?o.querySelector(".pipeline-date__save"):null;s&&(s.textContent="Save",s.classList.remove("pipeline-date__save--saved"),s.disabled=!1)})}),document.querySelectorAll(".pipeline-meet__input").forEach(function(n){n.addEventListener("input",function(){var o=n.closest(".pipeline-step__meet-row"),s=o?o.querySelector(".pipeline-meet__save"):null;s&&(s.textContent="Save Link",s.classList.remove("pipeline-date__save--saved"),s.style.background="var(--purple)",s.disabled=!1)})}),document.querySelectorAll(".pipeline-date__crystal").forEach(function(n){n.addEventListener("click",async function(){var o=n.dataset.id,s=n.dataset.title||"Event",i=n.closest(".pipeline-step__row"),r=i?i.querySelector(".pipeline-date__input"):null;if(!r||!r.value){alert("Please set a date/time first, then sync to Crystal.");return}var d=new Date(r.value),l=d.getFullYear()+"-"+String(d.getMonth()+1).padStart(2,"0")+"-"+String(d.getDate()).padStart(2,"0"),c=d.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"});n.disabled=!0,n.textContent="â³";try{var p=await fetch(O+"/calendar/sync",{method:"POST",headers:{Authorization:"Bearer "+e,"Content-Type":"application/json"},body:JSON.stringify({clientId:o,title:s,date:l,time:c})});if(p.ok)n.textContent="â
",n.title="Synced to Crystal!";else{var u=await p.json();n.textContent="â",n.title="Sync failed: "+(u.error||"Unknown error"),setTimeout(function(){n.textContent="ð
",n.disabled=!1},3e3)}}catch(g){n.textContent="â",n.title="Sync error: "+g.message,setTimeout(function(){n.textContent="ð
",n.disabled=!1},3e3)}})}),document.querySelectorAll(".pipeline-action-btn--archive").forEach(function(n){n.addEventListener("click",async function(){var o=n.dataset.id,s=n.dataset.action||"archive";if(s==="restore"){if(!confirm("Are you sure you want to restore this lead to the active pipeline?"))return;n.disabled=!0,n.textContent="Restoringâ¦";var i=await W(o,{status:"new"},e);if(i){var r=document.querySelector(".kanban-modal__close");r?r.click():xe(e)}else n.disabled=!1,n.innerHTML="<span>ð¤</span> Restore Lead",alert("Failed to restore lead. Please try again.")}else{if(!confirm("Are you sure you want to archive this lead? It will be hidden from the active pipeline."))return;n.disabled=!0,n.textContent="Archivingâ¦";var i=await W(o,{status:"archived"},e);if(i){var r=document.querySelector(".kanban-modal__close");r?r.click():xe(e)}else n.disabled=!1,n.innerHTML="<span>ðï¸</span> Archive Lead",alert("Failed to archive lead. Please try again.")}})}),document.querySelectorAll(".pipeline-action-btn--delete").forEach(function(n){n.addEventListener("click",async function(){var o=n.dataset.id;if(confirm("WARNING: Are you sure you want to permanently delete this lead? This action cannot be undone.")){n.disabled=!0,n.textContent="Deletingâ¦";try{var s=await fetch(O+"/clients/"+o,{method:"DELETE",headers:{Authorization:"Bearer "+e}});if(s.ok){var i=document.querySelector(".kanban-modal__close");i?i.click():xe(e)}else throw new Error("Delete failed")}catch{n.disabled=!1,n.innerHTML="<span>ðï¸</span> Delete Lead",alert("Failed to delete lead. Please try again.")}}})}),document.querySelectorAll(".timeline-tab").forEach(function(n){n.addEventListener("click",function(){var o=n.dataset.tab,s=n.dataset.id,i=n.closest(".pipeline-card");if(i){i.querySelectorAll(".timeline-tab").forEach(function(l){l.classList.remove("timeline-tab--active")}),n.classList.add("timeline-tab--active");var r=document.getElementById("tab-timeline-"+s),d=document.getElementById("tab-profile-"+s);o==="timeline"?(r&&(r.style.display="block"),d&&(d.style.display="none")):(r&&(r.style.display="none"),d&&(d.style.display="block"))}})})}function Yt(e){if(e<=0){var a=Math.abs(e),t=Math.floor(a/36e5),n=Math.floor(a%36e5/6e4);return{text:"OVERDUE by "+t+"h "+n+"m",urgent:"overdue"}}var o=Math.floor(e/36e5),s=Math.floor(e%36e5/6e4),i=o+"h "+s+"m remaining",r="normal";
2078return o<2?r="critical":o<6?r="warning":o<12&&(r="caution"),{text:i,urgent:r}}function ks(e){var a=document.getElementById("pipeline-schedule");if(a){var t=Date.now(),n=1440*60*1e3,o=new Set,s=e.filter(function(l){return!l.called&&!l.contactFailed}).map(function(l){var c=new Date(l.createdAt).getTime(),p=c+n,u=p-t,g=Math.max(0,Math.min(100,u/n*100)),y=Yt(u);return o.add(l.id),{client:l,remaining:u,pct:g,countdown:y,created:c}}).sort(function(l,c){return l.remaining-c.remaining}),i=e.filter(function(l){return l.discoveryCall==="scheduled"&&l.nextCallScheduled&&!o.has(l.id)}).map(function(l){var c=new Date(l.nextCallScheduled);return o.add(l.id),{client:l,date:c,isPast:c.getTime()<t}}).sort(function(l,c){return l.date-c.date}),r=e.filter(function(l){return l.onboarding&&l.status!=="converted"&&!o.has(l.id)});if(s.length===0&&i.length===0&&r.length===0){a.innerHTML="";return}var d='<div class="schedule-wrap"><div class="schedule-header"><h3>ð Upcoming Schedule</h3><span class="schedule-count">'+(s.length+i.length+r.length)+' items</span></div><div class="schedule-grid">';
2078s.forEach(function(l){var c=l.client,p=l.countdown.urgent,u=new Date(l.created).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit"});d+='<div class="schedule-item schedule-item--contact schedule-item--'+p+'"><div class="schedule-item__left"><span class="schedule-item__badge schedule-badge--'+p+'">ð Needs Contact</span><h4 class="schedule-item__name">'+(c.name||"â")+'</h4><span class="schedule-item__detail">'+(c.company||"â")+" ⢠"+(c.service||"â")+'</span><span class="schedule-item__submitted">Submitted: '+u+'</span></div><div class="schedule-item__right"><div class="schedule-timer schedule-timer--'+p+'"><span class="schedule-timer__text" data-deadline="'+(l.created+n)+'">'+l.countdown.text+'</span></div><div class="schedule-bar"><div class="schedule-bar__fill schedule-bar__fill--'+p+'" style="width:'+l.pct+'%"></div></div></div></div>'}),i.forEach(function(l){var c=l.client,p=l.date.toLocaleDateString("en-US",{weekday:"short",month:"short",day:"numeric"}),u=l.date.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"});l.isPast,d+='<div class="schedule-item schedule-item--discovery"><div class="schedule-item__left"><span class="schedule-item__badge schedule-badge--discovery">ð Discovery Call</span><h4 class="schedule-item__name">'+(c.name||"â")+'</h4><span class="schedule-item__detail">'+(c.company||"â")+'</span></div><div class="schedule-item__right"><div class="schedule-datetime"><span class="schedule-datetime__date">'+p+'</span><span class="schedule-datetime__time">'+u+"</span></div></div></div>"}),r.forEach(function(l){var c=bt(l);d+='<div class="schedule-item schedule-item--onboarding"><div class="schedule-item__left"><span class="schedule-item__badge schedule-badge--onboarding">ð Onboarding</span><h4 class="schedule-item__name">'+(l.name||"â")+'</h4><span class="schedule-item__detail">'+(l.company||"â")+'</span></div><div class="schedule-item__right"><span class="pipeline-card__status pipeline-card__status--'+c.cls+'">'+c.label+"</span></div></div>"}),d+="</div></div>",a.innerHTML=d}}function Ms(e){ot&&clearInterval(ot),ot=setInterval(function(){var a=Date.now();document.querySelectorAll(".schedule-timer__text").forEach(function(t){var n=parseInt(t.dataset.deadline,10),o=n-a,s=Yt(o);t.textContent=s.text;var i=t.closest(".schedule-timer");i&&(i.className="schedule-timer schedule-timer--"+s.urgent);var r=t.closest(".schedule-item");r&&(r.className=r.className.replace(/schedule-item--(normal|caution|warning|critical|overdue)/g,""),r.classList.add("schedule-item--"+s.urgent));var d=r?r.querySelector(".schedule-bar__fill"):null;if(d){var l=Math.max(0,Math.min(100,o/864e5*100));d.style.width=l+"%",d.className="schedule-bar__fill schedule-bar__fill--"+s.urgent}})},6e4)}async function W(e,a,t){try{const n=await fetch(O+"/clients/"+e,{method:"PATCH",headers:{"Content-Type":"application/json",Authorization:"Bearer "+t},body:JSON.stringify(a)});if(n.ok)return await n.json()}catch(n){console.error("patchClient error:",n)}return null}function Ts(){return` 2079 <section class="section"> 2080 <div class="container"> 2081 <div class="section__head"> 2082 <h2>Clients</h2> 2083 <p>List of intake submissions and clients</p> 2084 </div> 2085 <div class="glass" style="padding:12px;overflow:auto;"> 2086 <table id="clients-table" style="width:100%;border-collapse:collapse;"> 2087 <thead> 2088 <tr><th>Name</th><th>Email</th><th>Phone</th><th>Company</th><th>Status</th><th>Updated</th><th>Actions</th></tr> 2089 </thead> 2090 <tbody id="clients-tbody"></tbody> 2091 </table> 2092 </div> 2093 </div> 2094 </section> 2095 `}function Ss(e){const a=new Date(e.updatedAt||e.createdAt).toLocaleString();return` 2096 <tr data-id="${e.id}"> 2097 <td>${e.name||"-"}</td> 2098 <td>${e.email||"-"}</td> 2099 <td>${e.phone||"-"}</td> 2100 <td>${e.company||"-"}</td> 2101 <td> 2102 <select class="client-status-select"> 2103 <option value="new" ${e.status==="new"?"selected":""}>new</option> 2104 <option value="contacted" ${e.status==="contacted"?"selected":""}>contacted</option> 2105 <option value="interested" ${e.status==="interested"?"selected":""}>interested</option> 2106 <option value="in-program" ${e.status==="in-program"?"selected":""}>in-program</option> 2107 <option value="converted" ${e.status==="converted"?"selected":""}>converted</option> 2108 <option value="closed" ${e.status==="closed"?"selected":""}>closed</option> 2109 </select> 2110 </td> 2111 <td>${a}</td> 2112 <td> 2113 <button class="btn btn--small btn-view">View</button> 2114 <button class="btn btn--small btn-note">Note</button> 2115 </td> 2116 </tr> 2117 `}function Hs(){const e=localStorage.getItem("bizwrap_token");if(!e){window.location.hash="#/admin-login";return}(async function(){const t=await fetch(`${O}/clients`,{headers:{Authorization:`Bearer ${e}`}});if(!t.ok){if(t.status===401||t.status===403){localStorage.removeItem("bizwrap_token"),localStorage.removeItem("bizwrap_user"),window.location.hash="#/admin-login";return}throw new Error(`Server returned status ${t.status}`)}const n=await t.json(),o=document.getElementById("clients-tbody");o.innerHTML=n.map(Ss).join(""),o.querySelectorAll(".btn-view").forEach(s=>{s.addEventListener("click",i=>{const r=i.target.closest("tr").dataset.id;window.location.hash=`#/admin/clients/${r}`})}),o.querySelectorAll(".client-status-select").forEach(s=>{s.addEventListener("change",async i=>{const d=i.target.closest("tr").dataset.id,l=i.target.value;try{await fetch(`${O}/clients/${d}`,{method:"PATCH",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({status:l})})}catch(c){console.error(c)}})}),o.querySelectorAll(".btn-note").forEach(s=>{s.addEventListener("click",async i=>{const r=i.target.closest("tr").dataset.id,d=prompt("Quick note");if(d)try{await fetch(`${O}/clients/${r}/timeline`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({event:"note",note:d})}),alert("Note added")}catch(l){alert("Error"),console.error(l)}})})})()}const ee=e=>String(e??"").replace(/[&<>"']/g,a=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[a]),ye=e=>`"${String(e??"").replace(/"/g,"")}"`,Vt=e=>String(e||"").toLowerCase().replace(/[^a-z0-9]+/g,"").slice(0,24)||"tenant",Jt=e=>String(e||"").trim().replace(/^https?:\/\//i,"").replace(/\/.*$/,"");function Rs(e){const a=["node scripts/make-tenant.mjs","--name "+ye(e.companyName)];e.ownerName&&a.push("--owner "+ye(e.ownerName)),e.ownerEmail&&a.push("--email "+ye(e.ownerEmail));const t=Jt(e.website);return t&&a.push("--domain "+ye(t)),e.cityState&&a.push("--city "+ye(e.cityState)),e.brandColor&&a.push("--color "+ye(e.brandColor)),e.logoUrl?a.push("--logo "+ye(e.logoUrl)):e.logoDataUrl&&a.push('--logo "PATH-TO-DOWNLOADED-LOGO"'),a.join(" ")}function Cs(e){const a=e.questionnaire&&e.questionnaire.companyProfile,t="color:var(--muted);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:0.06em;";if(!a||!a.companyName)return e.questionnaire?`<div class="admin-detail__info-card" style="margin-top:var(--space-4);"> 2118 <h3 style="font-size:var(--text-base);font-weight:var(--weight-bold);margin-bottom:var(--space-3);">ð Crystal Demo Builder</h3> 2119 <p style="color:var(--muted);font-size:var(--text-sm);line-height:1.5;">This lead finished the discovery questionnaire before the Company Basics section existed, so there's no company profile to auto-build a demo from. Grab their company name, owner, website and city on the call, then run <code>make-demo.bat</code> in your Crystal folder.</p> 2120 </div>`:"";const n=Vt(a.companyName),o=Rs(a),s=!!a.logoDataUrl,i=a.logoDataUrl||a.logoUrl;return` 2121 <div class="admin-detail__info-card" id="crystal-demo-builder" style="margin-top:var(--space-4);border:1px solid var(--brand, #d9b25a);"> 2122 <h3 style="font-size:var(--text-base);font-weight:var(--weight-bold);margin-bottom:var(--space-4);">ð Crystal Demo Builder</h3> 2123 <div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3);font-size:var(--text-sm);margin-bottom:var(--space-4);"> 2124 <div><strong style="${t}">Company</strong><br/>${ee(a.companyName)}</div> 2125 <div><strong style="${t}">Owner</strong><br/>${ee(a.ownerName)||"â"}</div> 2126 <div><strong style="${t}">Owner email</strong><br/>${ee(a.ownerEmail)||"â"}</div> 2127 <div><strong style="${t}">Website</strong><br/>${ee(Jt(a.website))||"â"}</div> 2128 <div><strong style="${t}">City</strong><br/>${ee(a.cityState)||"â"}</div> 2129 <div><strong style="${t}">Brand color</strong><br/><span style="display:inline-block;width:13px;height:13px;border-radius:3px;vertical-align:middle;margin-right:6px;background:${ee(a.brandColor)||"#d9b25a"};"></span>${ee(a.brandColor)||"â"}</div> 2130 </div> 2131 ${i?`<div style="margin-bottom:var(--space-4);"><strong style="${t}">Logo</strong><br/><img src="${ee(i)}" alt="logo" style="max-height:56px;
2131max-width:200px;background:#fff;border-radius:8px;padding:6px;margin-top:6px;"/></div>`:""} 2132 2133 <strong style="${t}">1 · Build the tenant</strong> 2134 <p style="color:var(--muted);font-size:var(--text-xs);margin:4px 0 6px;line-height:1.5;">Run this in your <code>Crystal</code> folder.${s?" Download the logo (below) first, then replace <code>PATH-TO-DOWNLOADED-LOGO</code> with the saved file (or just drag the file onto the command).":""}</p> 2135 <pre id="demo-cmd" style="background:var(--code-bg,#0d1117);color:#e6edf3;border:1px solid var(--border);border-radius:8px;padding:12px 14px;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;line-height:1.55;white-space:pre-wrap;word-break:break-word;overflow-x:auto;margin:0;">${ee(o)}</pre> 2136 <div style="display:flex;gap:var(--space-2);flex-wrap:wrap;margin-top:var(--space-2);"> 2137 <button class="btn btn--primary" id="demo-copy-cmd" style="background:var(--grad-brand);">Copy command</button> 2138 ${s?'<button class="btn btn--secondary" id="demo-dl-logo">Download logo</button>':""} 2139 </div> 2140 2141 <p style="color:var(--muted);font-size:var(--text-xs);margin-top:var(--space-4);line-height:1.5;"><strong style="${t}">2 · Open it on the call</strong><br/><code>demo.bat ${ee(n)}</code> â launches Crystal branded as ${ee(a.companyName)} on your machine. The city was geocoded automatically; fine-tune coords in <code>web-app/src/tenants/${ee(n)}.js</code> if needed.</p> 2142 </div>`}function Is(){return` 2143 <section class="section" style="padding-top:calc(var(--nav-height) + var(--space-8));"> 2144 <div class="container admin-detail" id="admin-detail"> 2145 <div class="admin-empty"><div class="admin-empty__icon">â³</div><div class="admin-empty__text">Loading clientâ¦</div></div> 2146 </div> 2147 </section> 2148 `}function $s(){const e=localStorage.getItem("bizwrap_token");if(!e){window.location.hash="#/admin-login";return}const t=window.location.hash.split("/clients/")[1];if(!t){window.location.hash="#/admin";return}Kt(t,e)}async function Kt(e,a){try{const t=await fetch(`${O}/clients/${e}`,{headers:{Authorization:`Bearer ${a}`}});if(!t.ok)throw new Error("Not found");const n=await t.json();Ls(n,a)}catch{document.getElementById("admin-detail").innerHTML=` 2149 <div class="admin-empty"><div class="admin-empty__icon">â ï¸</div><div class="admin-empty__text">Client not found.</div></div> 2150 <div style="text-align:center;margin-top:var(--space-4);"><button class="btn btn--ghost" onclick="window.location.hash='#/admin'">â Back to Pipeline</button></div>`}}function Ls(e,a){const t=e,n=document.getElementById("admin-detail");n.innerHTML=` 2151 <div style="margin-bottom:var(--space-4);"> 2152 <button class="admin-action" onclick="window.location.hash='#/admin'" style="font-size:var(--text-sm);">â Back to Pipeline</button> 2153 </div> 2154 2155 <div class="admin-detail__header"> 2156 <div class="admin-detail__info-card"> 2157 <h2 style="font-size:var(--text-2xl);font-weight:var(--weight-black);margin-bottom:var(--space-4);">${t.name||"Unknown"}</h2> 2158 <div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3);font-size:var(--text-sm);"> 2159 <div><strong style="color:var(--muted);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:0.06em;">Email</strong><br/>${t.email||"â"}</div> 2160 <div><strong style="color:var(--muted);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:0.06em;">Phone</strong><br/>${t.phone||"â"}</div> 2161 <div><strong style="color:var(--muted);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:0.06em;">Company</strong><br/>${t.company||"â"}</div> 2162 <div><strong style="color:var(--muted);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:0.06em;">Service</strong><br/>${t.service||"â"}</div> 2163 <div><strong style="color:var(--muted);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:0.06em;">Source</strong><br/><span class="admin-source">${t.source||"manual"}</span></div> 2164 <div><strong style="color:var(--muted);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:0.06em;">Submitted</strong><br/>${new Date(t.createdAt).toLocaleDateString()}</div> 2165 </div> 2166 ${t.message?`<div style="margin-top:var(--space-5);padding-top:var(--space-4);border-top:1px solid var(--border);"><strong style="color:var(--muted);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:0.06em;">Original Message</strong><p style="font-size:var(--text-sm);color:var(--ink);margin-top:var(--space-2);">${t.message}</p></div>`:""} 2167 </div> 2168 2169 <div class="admin-detail__status-card"> 2170 <h3 style="font-size:var(--text-base);font-weight:var(--weight-bold);margin-bottom:var(--space-4);">Status Tracking</h3> 2171 <div class="admin-detail__status-row">
2172 <span class="admin-detail__status-label">Pipeline</span> 2173 <select class="admin-select" id="detail-status" data-field="status"> 2174 ${["new","contacted","interested","in-program","converted","closed"].map(s=>`<option value="${s}" ${t.status===s?"selected":""}>${s}</option>`).join("")} 2175 </select> 2176 </div> 2177 <div class="admin-detail__status-row"> 2178 <span class="admin-detail__status-label">Called</span> 2179 <label class="admin-toggle ${t.called?"admin-toggle--on":""}" id="detail-called" data-field="called"> 2180 <span class="admin-toggle__track"></span> 2181 </label> 2182 </div> 2183 <div class="admin-detail__status-row"> 2184 <span class="admin-detail__status-label">Interested</span> 2185 <div class="admin-tri" id="detail-interested" data-field="interested"> 2186 <button class="admin-tri__opt ${t.interested==="unknown"?"admin-tri__opt--active-unknown":""}" data-val="unknown">?</button> 2187 <button class="admin-tri__opt ${t.interested==="yes"?"admin-tri__opt--active-yes":""}" data-val="yes">Yes</button> 2188 <button class="admin-tri__opt ${t.interested==="no"?"admin-tri__opt--active-no":""}" data-val="no">No</button> 2189 </div> 2190 </div> 2191 <div class="admin-detail__status-row"> 2192 <span class="admin-detail__status-label">Discovery Call</span> 2193 <select class="admin-select" id="detail-discovery" data-field="discoveryCall"> 2194 ${["not-scheduled","scheduled","completed"].map(s=>`<option value="${s}" ${t.discoveryCall===s?"selected":""}>${s.replace("-"," ")}</option>`).join("")} 2195 </select> 2196 </div> 2197 <div class="admin-detail__status-row"> 2198 <span class="admin-detail__status-label">Onboarding</span> 2199 <label class="admin-toggle ${t.onboarding?"admin-toggle--on":""}" id="detail-onboarding" data-field="onboarding"> 2200 <span class="admin-toggle__track"></span> 2201 </label> 2202 </div> 2203 </div> 2204 </div> 2205 2206 ${Cs(t)} 2207 2208 <!-- Timeline --> 2209 <div class="admin-detail__info-card admin-timeline"> 2210 <h3 style="font-size:var(--text-base);font-weight:var(--weight-bold);margin-bottom:var(--space-4);">Timeline</h3> 2211 <div style="display:flex;gap:var(--space-3);margin-bottom:var(--space-5);"> 2212 <input class="form-input" id="detail-event" placeholder="Event (e.g. Follow-up call)" style="flex:1;" /> 2213 <input class="form-input" id="detail-note" placeholder="Note (optional)" style="flex:1;" /> 2214 <button class="btn btn--primary" id="detail-add-event">Add</button> 2215 </div> 2216 <div id="detail-timeline"> 2217 ${(t.timeline||[]).reverse().map(s=>` 2218 <div class="admin-timeline__item"> 2219 <div class="admin-timeline__dot"></div> 2220 <div class="admin-timeline__content"> 2221 <div class="admin-timeline__event">${s.event||"Note"}</div> 2222 ${s.note?`<div class="admin-timeline__note">${s.note}</div>`:""} 2223 <div class="admin-timeline__date">${new Date(s.date).toLocaleString()}</div> 2224 </div> 2225 </div> 2226 `).join("")||'<div style="color:var(--muted);font-size:var(--text-sm);">No events yet.</div>'} 2227 </div> 2228 </div> 2229 `;const o=t.id;document.querySelectorAll(".admin-toggle").forEach(s=>{s.addEventListener("click",async()=>{const i=s.dataset.field,r=s.classList.contains("admin-toggle--on");s.classList.toggle("admin-toggle--on"),await rt(o,{[i]:!r},a)})}),document.querySelectorAll(".admin-select").forEach(s=>{s.addEventListener("change",async()=>
2229{await rt(o,{[s.dataset.field]:s.value},a)})}),document.querySelectorAll(".admin-tri").forEach(s=>{s.querySelectorAll(".admin-tri__opt").forEach(i=>{i.addEventListener("click",async()=>{s.querySelectorAll(".admin-tri__opt").forEach(d=>d.className="admin-tri__opt");const r=i.dataset.val;r==="yes"?i.classList.add("admin-tri__opt--active-yes"):r==="no"?i.classList.add("admin-tri__opt--active-no"):i.classList.add("admin-tri__opt--active-unknown"),await rt(o,{interested:r},a)})})}),document.getElementById("demo-copy-cmd")?.addEventListener("click",async s=>{const i=document.getElementById("demo-cmd")?.textContent||"";try{await navigator.clipboard.writeText(i);const r=s.currentTarget,d=r.textContent;r.textContent="Copied â",setTimeout(()=>{r.textContent=d},1500)}catch{}}),document.getElementById("demo-dl-logo")?.addEventListener("click",()=>{const s=t.questionnaire&&t.questionnaire.companyProfile;if(!s||!s.logoDataUrl)return;const i=s.logoDataUrl.match(/^data:image\/(png|jpeg|jpg|webp|svg\+xml);/),r=i?i[1]==="jpeg"?"jpg":i[1]==="svg+xml"?"svg":i[1]:"png",d=document.createElement("a");d.href=s.logoDataUrl,d.download=`${Vt(s.companyName)}-logo.${r}`,document.body.appendChild(d),d.click(),d.remove()}),document.getElementById("detail-add-event")?.addEventListener("click",async()=>{const s=document.getElementById("detail-event")?.value,i=document.getElementById("detail-note")?.value;if(s)try{const d=await(await fetch(`${O}/clients/${o}/timeline`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({event:s,note:i})})).json();Kt(o,a)}catch{}})}async function rt(e,a,t){await fetch(`${O}/clients/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:JSON.stringify(a)})}function Bs(){return` 2230 <section class="section" style="padding-top:calc(var(--nav-height) + var(--space-8));"> 2231 <div class="container admin-dash"> 2232 <div class="admin-header"> 2233 <h1 class="admin-header__title">Admin Portal</h1> 2234 </div> 2235 2236 <!-- Admin Sub-Nav --> 2237 <div class="admin-nav"> 2238 <span class="admin-nav__link" onclick="window.location.hash='#/admin'">Pipeline</span> 2239 <span class="admin-nav__link admin-nav__link--active" onclick="window.location.hash='#/admin-forms'">Forms Builder</span> 2240 <span class="admin-nav__link" onclick="window.location.hash='#/admin-email'">Email Template</span> 2241 </div> 2242 2243 <!-- Create new form --> 2244 <div class="admin-table-wrap" style="margin-bottom:var(--space-6);"> 2245 <div class="admin-table-header"> 2246 <h3>Create New Form</h3> 2247 </div> 2248 <div style="padding:var(--space-5) var(--space-6);display:flex;gap:var(--space-3);"> 2249 <input class="form-input" id="new-form-name" placeholder="Form name (e.g. Client Intake)" style="flex:1;margin:0;" /> 2250 <input class="form-input" id="new-form-desc" placeholder="Description (optional)" style="flex:1;margin:0;" /> 2251 <button class="btn btn--primary" id="create-form-btn" style="white-space:nowrap;">+ Create</button> 2252 </div> 2253 </div> 2254 2255 <!-- Forms list --> 2256 <div id="forms-list-area"> 2257 <div class="admin-empty"><div class="admin-empty__icon">ð</div><div class="admin-empty__text">Loading formsâ¦</div></div> 2258 </div> 2259 2260 <!-- Form editor (hidden until a form is selected) --> 2261 <div id="form-editor-area" style="display:none;"> 2262 <div class="admin-table-wrap"> 2263 <div class="admin-table-header"> 2264 <div> 2265 <h3 id="editor-form-name" style="margin:0;"></h3> 2266 <span id="editor-form-desc" style="font-size:var(--text-xs);color:var(--muted);"></span> 2267 </div> 2268 <div style="display:flex;gap:var(--space-2);"> 2269 <button class="admin-action" id="editor-close-btn">â Close</button> 2270 <button class="btn btn--primary" id="editor-save-btn" style="font-size:var(--text-sm);padding:var(--space-2) var(--space-5);">Save Form</button> 2271 </div> 2272 </div> 2273 2274 <!-- Fields list --> 2275 <div style="padding:var(--space-5) var(--space-6);"> 2276 <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-4);">
2277 <span style="font-size:var(--text-sm);font-weight:var(--weight-bold);color:var(--ink);">Form Fields</span> 2278 <button class="admin-action" id="add-field-btn" style="color:var(--purple);border-color:var(--purple);">+ Add Field</button> 2279 </div> 2280 <div id="fields-container"></div> 2281 <div id="fields-empty" class="admin-empty" style="padding:var(--space-8) var(--space-4);"> 2282 <div class="admin-empty__icon">ð</div> 2283 <div class="admin-empty__text">No fields yet. Click "+ Add Field" to start building your form.</div> 2284 </div> 2285 </div> 2286 2287 <!-- Live preview --> 2288 <div style="border-top:1px solid var(--border);padding:var(--space-5) var(--space-6);"> 2289 <span style="font-size:var(--text-sm);font-weight:var(--weight-bold);color:var(--ink);display:block;margin-bottom:var(--space-4);">Live Preview</span> 2290 <div id="form-preview" style="background:var(--bg-alt);border-radius:var(--radius-md);padding:var(--space-6);"></div> 2291 </div> 2292 </div> 2293 </div> 2294 </div> 2295 </section> 2296 2297 <style> 2298 .fb-field { 2299 background: var(--bg-alt); 2300 border: 1px solid var(--border); 2301 border-radius: var(--radius-md); 2302 padding: var(--space-4); 2303 margin-bottom: var(--space-3); 2304 transition: box-shadow 0.15s ease, opacity 0.15s ease, transform 0.15s ease; 2305 position: relative; 2306 } 2307 .fb-field:hover { box-shadow: var(--shadow-sm); } 2308 /* Drag states */ 2309 .fb-field--dragging { 2310 opacity: 0.4; 2311 transform: scale(0.98); 2312 box-shadow: none; 2313 } 2314 .fb-field--drag-over { 2315 /* don't highlight, the indicator line handles this */ 2316 } 2317 .fb-drop-indicator { 2318 height: 4px; 2319 background: var(--purple); 2320 border-radius: 2px; 2321 margin: -2px 0; 2322 box-shadow: 0 0 8px rgba(104, 74, 197, 0.4); 2323 transition: opacity 0.15s ease; 2324 position: relative; 2325 z-index: 10; 2326 } 2327 .fb-drop-indicator::before { 2328 content: ''; 2329 position: absolute; 2330 left: -4px; top: -4px; 2331 width: 12px; height: 12px; 2332 border-radius: 50%; 2333 background: var(--purple); 2334 } 2335 .fb-drop-indicator::after { 2336 content: ''; 2337 position: absolute; 2338 right: -4px; top: -4px; 2339 width: 12px; height: 12px; 2340 border-radius: 50%; 2341 background: var(--purple); 2342 } 2343 .fb-field__header { 2344 display: flex; 2345 align-items: center; 2346 gap: var(--space-3); 2347 margin-bottom: var(--space-3); 2348 } 2349 .fb-field__grip { 2350 color: var(--muted-light); 2351 cursor: grab; 2352 font-size: 16px; 2353 line-height: 1; 2354 user-select: none; 2355 padding: 4px 2px; 2356 border-radius: 4px; 2357 transition: color 0.15s ease, background 0.15s ease; 2358 } 2359 .fb-field__grip:hover { color: var(--purple); background: var(--bg-tint); } 2360 .fb-field__grip:active { cursor: grabbing; color: var(--purple); } 2361 .fb-field__num { 2362 width: 24px; height: 24px; 2363 border-radius: 50%; 2364 background: var(--purple); 2365 color: #fff; 2366 font-size: 0.65rem; 2367 font-weight: var(--weight-bold); 2368 display: flex; 2369 align-items: center; 2370 justify-content: center; 2371 flex-shrink: 0; 2372 } 2373 .fb-field__body { 2374 display: grid; 2375 grid-template-columns: 1fr 140px 140px auto; 2376 gap: var(--space-3); 2377 align-items: start; 2378 } 2379 .fb-field__actions { 2380 display: flex; 2381 gap: var(--space-2); 2382 align-items: center; 2383 margin-left: auto; 2384 } 2385 .fb-field__remove { 2386 width: 28px; height: 28px; 2387 display: flex; 2388 align-items: center; 2389 justify-content: center; 2390 border: 1px solid var(--border); 2391 border-radius: var(--radius-full); 2392 background: #fff; 2393 color: var(--muted); 2394 cursor: pointer; 2395 font-size: 0.8rem; 2396 transition: all var(--transition-fast); 2397 } 2398 .fb-field__remove:hover { border-color: #dc2626;
2398 color: #dc2626; background: #fef2f2; } 2399 .fb-field__move { 2400 width: 28px; height: 28px; 2401 display: flex; 2402 align-items: center; 2403 justify-content: center; 2404 border: 1px solid var(--border); 2405 border-radius: var(--radius-full); 2406 background: #fff; 2407 color: var(--muted); 2408 cursor: pointer; 2409 font-size: 0.75rem; 2410 transition: all var(--transition-fast); 2411 } 2412 .fb-field__move:hover { border-color: var(--purple); color: var(--purple); } 2413 .fb-mini-input { 2414 padding: 5px 10px; 2415 font-size: var(--text-sm); 2416 border: 1px solid var(--border); 2417 border-radius: var(--radius-sm); 2418 background: #fff; 2419 color: var(--ink); 2420 width: 100%; 2421 } 2422 .fb-mini-input:focus { border-color: var(--purple); outline: none; } 2423 .fb-mini-select { 2424 padding: 5px 8px; 2425 font-size: var(--text-sm); 2426 border: 1px solid var(--border); 2427 border-radius: var(--radius-sm); 2428 background: #fff; 2429 color: var(--ink); 2430 width: 100%; 2431 cursor: pointer; 2432 } 2433 .fb-mini-select:focus { border-color: var(--purple); outline: none; } 2434 .fb-options-area { 2435 grid-column: 1 / -1; 2436 margin-top: var(--space-2); 2437 } 2438 .fb-required-toggle { 2439 display: flex; 2440 align-items: center; 2441 gap: 4px; 2442 cursor: pointer; 2443 user-select: none; 2444 font-size: var(--text-xs); 2445 color: var(--muted); 2446 white-space: nowrap; 2447 } 2448 .fb-form-card { 2449 display: flex; 2450 justify-content: space-between; 2451 align-items: center; 2452 padding: var(--space-4) var(--space-5); 2453 border-bottom: 1px solid var(--border); 2454 } 2455 .fb-form-card:last-child { border-bottom: none; } 2456 .fb-form-card:hover { background: var(--bg-alt); } 2457 @media (max-width: 700px) { 2458 .fb-field__body { grid-template-columns: 1fr; } 2459 } 2460 </style> 2461 `}let pe=[],Q=null,D=[],Te="";function Ps(){if(Te=localStorage.getItem("bizwrap_token"),!Te){window.location.hash="#/admin-login";return}document.getElementById("create-form-btn")?.addEventListener("click",qs),document.getElementById("add-field-btn")?.addEventListener("click",Os),document.getElementById("editor-save-btn")?.addEventListener("click",zs),document.getElementById("editor-close-btn")?.addEventListener("click",Xt),Oe()}const As=[{id:"name",label:"Full name",type:"text",required:!0,placeholder:"Joey Murphy",options:[]},{id:"email",label:"Email address",type:"email",required:!0,placeholder:"[email protected]",options:[]},{id:"company",label:"Company",type:"text",required:!1,placeholder:"Your business",options:[]},{id:"service",label:"What are you looking to build?",type:"select",required:!1,placeholder:"Select an option...",options:["Full operating system","AI agent / copilot","Property mapping & field tools","Design studio","Warehouse & logistics","Financials & reporting","Other / not sure yet"]},{id:"message",label:"Tell us about your operation",type:"textarea",required:!0,placeholder:"How does your business run today? Where does it hurt?",options:[]}];async function Oe(){try{const e=await fetch(`${O}/forms`,{headers:{Authorization:`Bearer ${Te}`}});if(!e.ok){if(e.status===401||e.status===403){localStorage.removeItem("bizwrap_token"),localStorage.removeItem("bizwrap_user"),window.location.hash="#/admin-login";return}throw new Error(`Server returned status ${e.status}`)}if(pe=await e.json(),pe.length===0){await Gs();return}Ns(),pe.length>0&&!Q&&Qt(pe[0].id)}catch{document.getElementById("forms-list-area").innerHTML=` 2462 <div class="admin-empty"><div class="admin-empty__icon">â ï¸</div><div class="admin-empty__text">Failed to load forms. Is the server running?</div></div>`}}async function Gs(){try{await fetch(`${O}/forms`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${Te}`},body:JSON.stringify({name:"Contact Form",description:'Main website intake form â "Book a Demo" / "Send us a message"',fields:As})}),await Oe()}catch{}}async function qs(){const e=document.getElementById("new-form-name"),a=document.getElementById("new-form-desc"),t=e?.value?.trim();if(!t){e?.focus();return}await fetch(`${O}/forms`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${Te}`},body:JSON.stringify({name:t,description:a?.value?.trim()||"",fields:[]})}),e.value="",a.value="",await Oe()}async function zs(){if(!Q)return;Ws(),await fetch(`${O}/forms/${Q.id}`,{method:"PATCH",headers:{"Content-Type":"application/json",Authorization:`Bearer ${Te}`},body:JSON.stringify({name:Q.name,description:Q.description,fields:D})});const e=document.getElementById("editor-save-btn");if(e){const a=e.textContent;e.textContent="â Saved!",e.style.background="#16a34a",setTimeout(()=>{e.textContent=a,e.style.background=""},1500)}await Oe()}async function Ds(e){confirm("Delete this form permanently?")&&(await fetch(`${O}/forms/${e}`,{method:"DELETE",headers:{Authorization:`Bearer ${Te}`}}),Q?.id===e&&Xt(),await Oe())}function Ns(){const e=document.getElementById("forms-list-area");if(pe.length===0){e.innerHTML=` 2463 <div class="admin-empty"><div class="admin-empty__icon">ð</div><div class="admin-empty__text">No forms yet. Create one above.</div></div>`;return}e.innerHTML=` 2464 <div class="admin-table-wrap"> 2465 <div class="admin-table-header"> 2466 <h3>Your Forms</h3>
2467 <span class="admin-table-count">${pe.length} form${pe.length!==1?"s":""}</span> 2468 </div> 2469 ${pe.map(a=>` 2470 <div class="fb-form-card" data-form-id="${a.id}"> 2471 <div> 2472 <strong style="font-size:var(--text-sm);">${a.name||a.title||"Untitled"}</strong> 2473 ${a.description?`<div style="font-size:var(--text-xs);color:var(--muted);margin-top:2px;">${a.description}</div>`:""} 2474 <div style="font-size:var(--text-xs);color:var(--muted-light);margin-top:2px;">${(a.fields||[]).length} field${(a.fields||[]).length!==1?"s":""} · ${a.active!==!1?'<span style="color:#16a34a;">Active</span>':'<span style="color:var(--muted);">Inactive</span>'}</div> 2475 </div> 2476 <div style="display:flex;gap:var(--space-2);"> 2477 <button class="admin-action fb-edit-btn" data-id="${a.id}" style="color:var(--purple);border-color:var(--purple);">Edit</button> 2478 <button class="admin-action fb-delete-btn" data-id="${a.id}">Delete</button> 2479 </div> 2480 </div> 2481 `).join("")} 2482 </div>`,e.querySelectorAll(".fb-edit-btn").forEach(a=>{a.addEventListener("click",()=>Qt(a.dataset.id))}),e.querySelectorAll(".fb-delete-btn").forEach(a=>{a.addEventListener("click",()=>Ds(a.dataset.id))})}function Qt(e){Q=pe.find(a=>a.id===e),Q&&(D=(Q.fields||[]).map((a,t)=>({id:a.id||`field_${Date.now()}_${t}`,label:a.label||"",type:a.type||"text",required:a.required||!1,placeholder:a.placeholder||"",options:a.options||[]})),document.getElementById("editor-form-name").textContent=Q.name||Q.title||"Untitled",document.getElementById("editor-form-desc").textContent=Q.description||"",document.getElementById("form-editor-area").style.display="block",Se(),ne())}function Xt(){Q=null,D=[],document.getElementById("form-editor-area").style.display="none"}function Os(){D.push({id:`field_${Date.now()}`,label:"",type:"text",required:!1,placeholder:"",options:[]}),Se(),ne(),setTimeout(()=>{const e=document.querySelectorAll(".fb-label-input");e[e.length-1]?.focus()},50)}function Fs(e){D.splice(e,1),Se(),ne()}function Ct(e,a){const t=e+a;t<0||t>=D.length||([D[e],D[t]]=[D[t],D[e]],Se(),ne())}function Se(){const e=document.getElementById("fields-container"),a=document.getElementById("fields-empty");if(D.length===0){e.innerHTML="",a.style.display="";return}a.style.display="none",e.innerHTML=D.map((t,n)=>` 2483 <div class="fb-field" data-index="${n}" draggable="true"> 2484 <div class="fb-field__header"> 2485 <span class="fb-field__grip" title="Drag to reorder">â ¿</span> 2486 <span class="fb-field__num">${n+1}</span> 2487 <div class="fb-field__body"> 2488 <input class="fb-mini-input fb-label-input" data-index="${n}" data-prop="label" value="${qe(t.label)}" placeholder="Field label" /> 2489 <select class="fb-mini-select fb-type-select" data-index="${n}" data-prop="type"> 2490 <option value="text" ${t.type==="text"?"selected":""}>Text</option> 2491 <option value="email" ${t.type==="email"?"selected":""}>Email</option> 2492 <option value="tel" ${t.type==="tel"?"selected":""}>Phone</option> 2493 <option value="textarea" ${t.type==="textarea"?"selected":""}>Textarea</option> 2494 <option value="select" ${t.type==="select"?"selected":""}>Dropdown</option> 2495 <option value="checkbox" ${t.type==="checkbox"?"selected":""}>Checkbox</option> 2496 </select> 2497 <input class="fb-mini-input" data-index="${n}" data-prop="placeholder" value="${qe(t.placeholder)}" placeholder="Placeholder" /> 2498 <div style="display:flex;align-items:center;gap:var(--space-2);"> 2499 <label class="fb-required-toggle"> 2500 <input type="checkbox" class="fb-req-check" data-index="${n}" ${t.required?"checked":""} style="accent-color:var(--purple);" /> 2501 Req 2502 </label> 2503 </div> 2504 </div> 2505 <div class="fb-field__actions"> 2506 <button class="fb-field__move fb-move-up" data-index="${n}" title="Move up" ${n===0?'disabled style="opacity:0.3;pointer-events:none;"':""}>â²</button> 2507 <button class="fb-field__move fb-move-down" data-index="${n}" title="Move down" ${n===D.length-1?'disabled style="opacity:0.3;pointer-events:none;"':""}>â¼</button> 2508 <button class="fb-field__remove fb-remove" data-index="${n}" title="Remove field">â</button> 2509 </div> 2510 </div> 2511 ${t.type==="select"?` 2512 <div class="fb-options-area"> 2513 <label style="font-size:var(--text-xs);font-weight:var(--weight-semibold);color:var(--muted);display:block;margin-bottom:var(--space-1);">Options (comma-separated)</label> 2514 <input class="fb-mini-input fb-options-input" data-index="${n}" value="${qe((t.options||[]).join(", "))}" placeholder="Option 1, Option 2, Option 3" /> 2515 </div>`:""} 2516 </div> 2517 `).join(""),e.querySelectorAll('.fb-label-input, .fb-mini-input[data-prop="placeholder"]').forEach(t=>{t.addEventListener("input",()=>{const n=parseInt(t.dataset.index),o=t.dataset.prop;D[n][o]=t.value,ne()})}),e.querySelectorAll(".fb-type-select").forEach(t=>{t.addEventListener("change",()=>{const n=parseInt(t.dataset.index);D[n].type=t.value,t.value!=="select"&&(D[n].options=[]),Se(),ne()})}),e.querySelectorAll(".fb-req-check").forEach(t=>{t.addEventListener("change",()=>{const n=parseInt(t.dataset.index);D[n].required=t.checked,ne()})}),e.querySelectorAll(".fb-options-input").forEach(t=>{t.addEventListener("input",()=>{const n=parseInt(t.dataset.index);D[n].options=t.value.split(",").map(o=>
2517o.trim()).filter(Boolean),ne()})}),e.querySelectorAll(".fb-move-up").forEach(t=>{t.addEventListener("click",()=>Ct(parseInt(t.dataset.index),-1))}),e.querySelectorAll(".fb-move-down").forEach(t=>{t.addEventListener("click",()=>Ct(parseInt(t.dataset.index),1))}),e.querySelectorAll(".fb-remove").forEach(t=>{t.addEventListener("click",()=>Fs(parseInt(t.dataset.index)))}),js(e)}function js(e){let a=null;function t(){return Array.from(e.querySelectorAll(".fb-field"))}function n(){e.querySelectorAll(".fb-drop-indicator").forEach(s=>s.remove()),t().forEach(s=>{s.classList.remove("fb-field--drag-over")})}function o(s,i){const r=i.getBoundingClientRect(),d=r.top+r.height/2;return s.clientY<d?"before":"after"}t().forEach(s=>{s.addEventListener("mousedown",i=>{i.target.closest(".fb-field__grip")?s.setAttribute("draggable","true"):s.setAttribute("draggable","false")}),s.addEventListener("dragstart",i=>{a=parseInt(s.dataset.index),s.classList.add("fb-field--dragging"),i.dataTransfer.effectAllowed="move",i.dataTransfer.setData("text/plain",String(a))}),s.addEventListener("dragover",i=>{if(i.preventDefault(),i.dataTransfer.dropEffect="move",parseInt(s.dataset.index)===a)return;n();const d=o(i,s),l=document.createElement("div");l.className="fb-drop-indicator",d==="before"?s.parentNode.insertBefore(l,s):s.parentNode.insertBefore(l,s.nextSibling)}),s.addEventListener("dragleave",i=>{s.contains(i.relatedTarget)||n()}),s.addEventListener("drop",i=>{i.preventDefault(),i.stopPropagation(),n();const r=a,d=parseInt(s.dataset.index);if(r===null||r===d)return;const l=o(i,s);let c=d;l==="after"&&(c+=1),r<c&&(c-=1);const[p]=D.splice(r,1);D.splice(c,0,p),a=null,Se(),ne()}),s.addEventListener("dragend",()=>{s.classList.remove("fb-field--dragging"),n(),a=null})}),e.addEventListener("dragover",s=>{s.preventDefault()}),e.addEventListener("drop",s=>{if(s.target===e||s.target.closest(".fb-field")===null){s.preventDefault(),n();const i=a;if(i===null)return;const[r]=D.splice(i,1);D.push(r),a=null,Se(),ne()}})}function ne(){const e=document.getElementById("form-preview");if(e){if(D.length===0){e.innerHTML='<div style="text-align:center;color:var(--muted);font-size:var(--text-sm);padding:var(--space-6);">Add fields to see a live preview</div>';return}e.innerHTML=D.map(a=>{const t=a.required?' <span style="color:#dc2626;">*</span>':"",n=`<label style="display:block;font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--ink);margin-bottom:var(--space-1);">${a.label||"Untitled"}${t}</label>`;switch(a.type){case"textarea":return`<div style="margin-bottom:var(--space-4);">${n}<textarea class="form-input" placeholder="${qe(a.placeholder)}" style="min-height:80px;" disabled></textarea></div>`;case"select":return`<div style="margin-bottom:var(--space-4);">${n}<select class="form-input" disabled><option>${a.placeholder||"Select..."}</option>${(a.options||[]).map(o=>`<option>${o}</option>`).join("")}</select></div>`;case"checkbox":return`<div style="margin-bottom:var(--space-4);"><label style="display:flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm);color:var(--ink);cursor:pointer;"><input type="checkbox" disabled style="accent-color:var(--purple);" /> ${a.label||"Untitled"}${t}</label></div>`;default:return`<div style="margin-bottom:var(--space-4);">${n}<input class="form-input" type="${a.type}" placeholder="${qe(a.placeholder)}" disabled /></div>`}}).join("")}}function Ws(){D.forEach((e,a)=>{e.id||(e.id=`field_${Date.now()}_${a}`)})}function qe(e){return(e||"").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}let B=null,De="",It=null;function Us(){return` 2518 <section class="section" style="padding-top:calc(var(--nav-height) + var(--space-8));"> 2519 <div class="container admin-dash"> 2520 <div class="admin-header"> 2521 <h1 class="admin-header__title">Admin Portal</h1> 2522 </div> 2523 2524 <!-- Admin Sub-Nav --> 2525 <div class="admin-nav"> 2526 <span class="admin-nav__link" onclick="window.location.hash='#/admin'">Pipeline</span> 2527 <span class="admin-nav__link" onclick="window.location.hash='#/admin-forms'">Forms Builder</span>
2528 <span class="admin-nav__link admin-nav__link--active" onclick="window.location.hash='#/admin-email'">Email Template</span> 2529 </div> 2530 2531 <div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-6);align-items:start;"> 2532 <!-- Editor Panel --> 2533 <div class="admin-table-wrap"> 2534 <div class="admin-table-header"> 2535 <h3>Edit Template</h3> 2536 <button class="btn btn--primary" id="et-save-btn" style="font-size:var(--text-sm);padding:var(--space-2) var(--space-5);">Save Template</button> 2537 </div> 2538 <div style="padding:var(--space-5) var(--space-6);" id="et-editor"> 2539 <div class="admin-empty"><div class="admin-empty__icon">âï¸</div><div class="admin-empty__text">Loading templateâ¦</div></div> 2540 </div> 2541 </div> 2542 2543 <!-- Live Preview Panel --> 2544 <div class="admin-table-wrap" style="position:sticky;top:calc(var(--nav-height) + var(--space-4));"> 2545 <div class="admin-table-header"> 2546 <h3>Live Preview</h3> 2547 <span style="font-size:var(--text-xs);color:var(--muted);">Updates as you type</span> 2548 </div> 2549 <div style="padding:var(--space-4);background:var(--bg-alt);" id="et-preview"> 2550 <div class="admin-empty" style="padding:var(--space-8);"><div class="admin-empty__text">Loading previewâ¦</div></div> 2551 </div> 2552 </div> 2553 </div> 2554 2555 <!-- Template Variables Reference --> 2556 <div class="admin-table-wrap" style="margin-top:var(--space-6);"> 2557 <div class="admin-table-header"> 2558 <h3>Template Variables</h3> 2559 </div> 2560 <div style="padding:var(--space-4) var(--space-6);font-size:var(--text-sm);color:var(--muted);"> 2561 <p style="margin-bottom:var(--space-2);">Use these placeholders in your subject line and body. They'll be replaced with the submitter's data:</p> 2562 <div style="display:flex;flex-wrap:wrap;gap:var(--space-2);"> 2563 <code style="background:var(--bg-tint);padding:2px 8px;border-radius:var(--radius-sm);color:var(--purple);font-size:var(--text-xs);font-weight:600;">{{name}}</code> 2564 <code style="background:var(--bg-tint);padding:2px 8px;border-radius:var(--radius-sm);color:var(--purple);font-size:var(--text-xs);font-weight:600;">{{email}}</code> 2565 <code style="background:var(--bg-tint);padding:2px 8px;border-radius:var(--radius-sm);color:var(--purple);font-size:var(--text-xs);font-weight:600;">{{company}}</code> 2566 <code style="background:var(--bg-tint);padding:2px 8px;border-radius:var(--radius-sm);color:var(--purple);font-size:var(--text-xs);font-weight:600;">{{service}}</code> 2567 <code style="background:var(--bg-tint);padding:2px 8px;border-radius:var(--radius-sm);color:var(--purple);font-size:var(--text-xs);font-weight:600;">{{message}}</code> 2568 </div> 2569 </div> 2570 </div> 2571 </div> 2572 </section> 2573 `}function Ys(){if(De=localStorage.getItem("bizwrap_token"),!De){window.location.hash="#/admin-login";return}document.getElementById("et-save-btn")?.addEventListener("click",Ks),Vs()}async function Vs(){try{const e=await fetch(`${O}/email-template`,{headers:{Authorization:`Bearer ${De}`}});if(!e.ok){if(e.status===401||e.status===403){localStorage.removeItem("bizwrap_token"),localStorage.removeItem("bizwrap_user"),window.location.hash="#/admin-login";return}throw new Error(`Server returned status ${e.status}`)}B=await e.json(),Js(),Zt()}catch{document.getElementById("et-editor").innerHTML='<div class="admin-empty"><div class="admin-empty__icon">â ï¸</div><div class="admin-empty__text">Failed to load template. Is the server running?</div></div>'}}function Js(){if(!B)return;const e=document.getElementById("et-editor");e.innerHTML=` 2574 <div style="margin-bottom:var(--space-5);"> 2575 <label style="font-size:var(--text-xs);font-weight:var(--weight-bold);text-transform:uppercase;letter-spacing:0.06em;color:var(--muted);display:block;margin-bottom:var(--space-1);">Subject Line</label> 2576 <input class="form-input et-field" data-field="subject" value="${ie(B.subject)}" style="margin:0;" /> 2577 </div> 2578 2579 <div style="margin-bottom:var(--space-5);"> 2580 <label style="font-size:var(--text-xs);font-weight:var(--weight-bold);text-transform:uppercase;letter-spacing:0.06em;color:var(--muted);display:block;margin-bottom:var(--space-1);">Preheader Text</label> 2581 <input class="form-input et-field" data-field="preheader" value="${ie(B.preheader)}" placeholder="Preview text shown in inbox" style="margin:0;" /> 2582 </div> 2583 2584 <div style="margin-bottom:var(--space-5);"> 2585 <label style="font-size:var(--text-xs);font-weight:var(--weight-bold);text-transform:uppercase;letter-spacing:0.06em;color:var(--muted);display:block;margin-bottom:var(--space-1);">Email Heading</label> 2586 <input class="form-input et-field" data-field="heading" value="${ie(B.heading)}" style="margin:0;" /> 2587 </div> 2588 2589 <div style="margin-bottom:var(--space-5);"> 2590 <label style="font-size:var(--text-xs);font-weight:var(--weight-bold);text-transform:uppercase;letter-spacing:0.06em;color:var(--muted);display:block;margin-bottom:var(--space-1);">Body</label> 2591 <textarea class="form-input et-field" data-field="bodyHtml" rows="8" style="margin:0;font-size:var(--text-sm);line-height:1.6;">${ie(Qs(B.bodyHtml))}</textarea> 2592 </div> 2593 2594 <div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4);margin-bottom:var(--space-5);"> 2595 <div> 2596 <label style="font-size:var(--text-xs);font-weight:var(--weight-bold);text-transform:uppercase;letter-spacing:0.06em;color:var(--muted);display:block;margin-bottom:var(--space-1);">Button Text</label> 2597 <input class="form-input et-field" data-field="ctaText" value="${ie(B.ctaText)}" style="margin:0;" /> 2598 </div> 2599 <div> 2600 <label style="font-size:var(--text-xs);font-weight:var(--weight-bold);text-transform:uppercase;letter-spacing:0.06em;color:var(--muted);display:block;margin-bottom:var(--space-1);">Button URL</label> 2601 <input class="form-input et-field" data-field="ctaUrl" value="${ie(B.ctaUrl)}" placeholder="https://" style="margin:0;" /> 2602 </div> 2603 </div> 2604 2605 <div style="margin-bottom:var(--space-5);"> 2606 <label style="font-size:var(--text-xs);font-weight:var(--weight-bold);text-transform:uppercase;letter-spacing:0.06em;color:var(--muted);display:block;margin-bottom:var(--space-1);">Footer Text</label> 2607 <input class="form-input et-field" data-field="footerText" value="${ie(B.footerText)}" style="margin:0;" /> 2608 </div> 2609 2610 <div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4);"> 2611 <div> 2612 <label style="font-size:var(--text-xs);font-weight:var(--weight-bold);text-transform:uppercase;letter-spacing:0.06em;color:var(--muted);display:block;margin-bottom:var(--space-1);">Brand Color</label> 2613 <div style="display:flex;gap:var(--space-2);align-items:center;"> 2614 <input type="color" class="et-field" data-field="brandColor" value="${B.brandColor||"#684ac5"}" style="width:40px;height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);cursor:pointer;padding:2px;" /> 2615 <input class="form-input et-field" data-field="brandColor" value="${ie(B.brandColor)}" style="margin:0;flex:1;font-family:'Courier New',monospace;" /> 2616 </div> 2617 </div> 2618 <div> 2619 <label style="font-size:var(--text-xs);font-weight:var(--weight-bold);text-transform:uppercase;letter-spacing:0.06em;color:var(--muted);display:block;margin-bottom:var(--space-1);">Accent Color</label> 2620 <div style="display:flex;gap:var(--space-2);align-items:center;"> 2621 <input type="color" class="et-field" data-field="accentColor" value="${B.accentColor||"#01c7cc"}" style="width:40px;height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);cursor:pointer;padding:2px;" /> 2622 <input class="form-input et-field" data-field="accentColor" value="${ie(B.accentColor)}" style="margin:0;flex:1;font-family:'Courier New',monospace;" /> 2623 </div> 2624 </div> 2625 </div> 2626 `,e.querySelectorAll(".et-field").forEach(a=>{const t=()=>
2626{const n=a.dataset.field,o=a.type==="checkbox"?a.checked:a.value;n==="bodyHtml"?B[n]=o.split(` 2627`).filter(s=>s.trim()).map(s=>"<p>"+s+"</p>").join(""):B[n]=o,(n==="brandColor"||n==="accentColor")&&e.querySelectorAll(`.et-field[data-field="${n}"]`).forEach(s=>{s!==a&&(s.value=o)}),clearTimeout(It),It=setTimeout(Zt,300)};a.addEventListener("input",t),a.addEventListener("change",t)})}async function Zt(){if(!B)return;const e=document.getElementById("et-preview");try{const a=await fetch(`${O}/email-template/preview`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${De}`},body:JSON.stringify({template:{subject:B.subject,preheader:B.preheader,heading:B.heading,bodyHtml:B.bodyHtml,ctaText:B.ctaText,ctaUrl:B.ctaUrl,footerText:B.footerText,brandColor:B.brandColor,accentColor:B.accentColor,logoUrl:B.logoUrl}})}),{html:t}=await a.json();e.innerHTML='<iframe id="et-preview-frame" style="width:100%;border:none;border-radius:var(--radius-md);background:#fff;min-height:500px;" sandbox="allow-same-origin"></iframe>';const n=document.getElementById("et-preview-frame"),o=n.contentDocument||n.contentWindow.document;o.open(),o.write(t),o.close(),setTimeout(()=>{n.style.height=o.body.scrollHeight+20+"px"},100)}catch{e.innerHTML='<div class="admin-empty" style="padding:var(--space-6);"><div class="admin-empty__text">Preview unavailable â is the server running?</div></div>'}}async function Ks(){if(!B?.id)return;const e=document.getElementById("et-save-btn");try{if(await fetch(`${O}/email-template/${B.id}`,{method:"PATCH",headers:{"Content-Type":"application/json",Authorization:`Bearer ${De}`},body:JSON.stringify({subject:B.subject,preheader:B.preheader,heading:B.heading,bodyHtml:B.bodyHtml,ctaText:B.ctaText,ctaUrl:B.ctaUrl,footerText:B.footerText,brandColor:B.brandColor,accentColor:B.accentColor,logoUrl:B.logoUrl})}),e){const a=e.textContent;e.textContent="â Saved!",e.style.background="#16a34a",setTimeout(()=>{e.textContent=a,e.style.background=""},1500)}}catch{e&&(e.textContent="â Error",e.style.background="#dc2626",setTimeout(()=>{e.textContent="Save Template",e.style.background=""},2e3))}}function ie(e){return(e||"").replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}function Qs(e){return e?e.replace(/<p>/gi,"").replace(/<\/p>/gi,` 2628`).replace(/<br\s*\/?>/gi,` 2629`).trim():""}function Xs(){return hs()}function Zs(){ms()}function en(){return gs()}function tn(){ys()}function an(){return Ts()}function sn(){Hs()}function nn(){return Is()}function on(){$s()}function rn(){return Bs()}function ln(){Ps()}function dn(){return Us()}function cn(){Ys()}const ht={home:{render:Et,init:_t},services:{render:Et,init:_t},contact:{render:wa,init:Ea},workshop:{render:Ma,init:Ta},chess:{render:ja,init:is},questionnaire:{render:rs,init:ls},"admin-login":{render:Xs,init:Zs},admin:{render:en,init:tn},"admin-clients":{render:an,init:sn},"admin-client":{render:nn,init:on},"admin-forms":{render:rn,init:ln},"admin-email":{render:dn,init:cn}};let Re=null,Ae=null,Je=!1,mt=null;function $t(){const e=window.location.hash.replace("#/","");if(!e)return"home";if(e.startsWith("admin/clients/"))return"admin-client";if(ht[e])return e;const a=e.split("/")[0];return ht[a]?a:"home"}async function Lt(e){if(e===Re)return;const a=ht[e];if(!a)return;if((e.startsWith("admin")||e==="admin-login")&&!Je&&mt&&await mt,e.startsWith("admin")&&e!=="admin-login"){const d=!!["localhost","127.0.0.1"].includes(window.location.hostname)&&localStorage.getItem("bizwrap_token")==="mock-local-token";if(!Ae&&!d){window.location.hash="#/admin-login";return}try{if(Ae){const l=await Ae.getIdToken();localStorage.setItem("bizwrap_token",l)}}catch(l){console.warn("Failed to refresh token during navigation:",l)}}const n=!!["localhost","127.0.0.1"].includes(window.location.hostname)&&localStorage.getItem("bizwrap_token")==="mock-local-token";if(e==="admin-login"&&(Ae||n)){window.location.hash="#/admin";return}Re=e;const o=document.getElementById("app"),s=document.getElementById("page-content");s?(s.style.animation="none",s.offsetHeight,s.innerHTML=a.render(),s.style.animation="pageIn 0.4s ease forwards"):(o.innerHTML=` 2630 ${ta()} 2631 <main id="page-content"> 2632 ${a.render()} 2633 </main> 2634 ${sa()} 2635 `,aa()),window.scrollTo({top:0,behavior:"instant"}),a.init(),Bt();const i={home:"BizWrap AI â Gift Wrap Your Business",services:"Platform â BizWrap AI",contact:"Contact â BizWrap AI",workshop:"AI Workshop â BizWrap AI",chess:"Chess Arena â BizWrap AI"};document.title=i[e]||i.home}function pn(){mt=new Promise(e=>{et().then(({auth:a,onAuthStateChanged:t})=>{t(a,async n=>{if(Ae=n,n)try{const o=await n.getIdToken(!0);localStorage.setItem("bizwrap_token",o),localStorage.setItem("bizwrap_user",n.displayName||n.email.split("@")[0])}catch(o){console.warn("Token refresh failed:",o)}if(Je||(Je=!0,e()),!n&&Re&&Re.startsWith("admin")&&Re!=="admin-login"){const o=!["localhost","127.0.0.1"].includes(window.location.hostname),s=localStorage.getItem("bizwrap_token");(o||s!=="mock-local-token")&&(window.location.hash="#/admin-login")}n&&Re==="admin-login"&&(window.location.hash="#/admin")})}).catch(a=>{console.warn("Firebase auth unavailable:",a),Je=!0,e()})}),window.addEventListener("hashchange",()=>{Lt($t())}),document.addEventListener("click",e=>{const a=e.target.closest("[data-navigate]");if(a){e.preventDefault();const t=a.dataset.navigate;window.location.hash=`#/${t}`}}),window.location.hash?Lt($t()):window.location.hash="#/home"}document.addEventListener("DOMContentLoaded",()=>{pn()});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.