import{l as Le}from"./schema-B1NKxfZc.js";import{m as he}from"./spine-showcase-BBAaV6oO.js";import{C as ke,L as Ee,b as ge,a as _e,S as q}from"./classes-DoJU-13A.js";const fe=ke.filter(t=>!!t.spine),ye=fe.flatMap(t=>[`${t.spine}_M`,`${t.spine}_F`]),me=(t,o)=>`${t}_${o?"M":"F"}`,xe=t=>({spine:t.replace(/_[MF]$/,""),male:!t.endsWith("_F")}),ve=t=>{for(;t.firstChild;)t.removeChild(t.firstChild)},n=t=>{const o=document.createElement("div");return o.innerHTML=t.trim(),o.firstElementChild},be=16/9,b=(t,o)=>`${t/o*100}%`,Ce={Prince:"Prince",Knight:"Knight",Elf:"Elf",Wizard:"Wizard",Darkelf:"Darkelf",DKnight:"DKnight",Illusionist:"Illusionist",Warrior:"Warrior",Fencer:"Fencer"};let w="/assets/v1",C=`${w}/ui/charselect`;const E={rosterW:384,panelW:400,headH:0,sexH:0,selW:1100,selBottom:500,selFull:1},K="l15.spineLayout";let g={...E};const Ne=Ee.map(t=>t.id),Y=t=>t==="login"?Ne:ye,J={login:{label:"Login screen",get stage(){return{left:"0",top:"0",width:"100%",height:"100%"}},chrome:()=>{const t=n('<div class="ch realui loginch"></div>'),o=n('<div class="mock screen login"></div>');return o.append(_e({account:"demo",inert:!0}).box),t.append(o),[t]}},create:{label:"Creation screen",get stage(){return{left:"0",top:"0",width:"100%",height:"100%"}},chrome:t=>{const{spine:o,male:l}=xe(t.set),u=(d,h)=>`${w}/ui/charselect/Login_Bt_Slot_${d.spine}_${l?"M":"F"}_${h?"F":"N"}.png`,p=n(`<div class="ch left" style="top:0;width:${b(g.rosterW,1920)}">
          <div class="brand"><img src="${w}/ui/charselect/Login_Ico_Character.png" alt="">
            <div><h1>Select a class</h1><p>Choose a class and gender, then name your character.</p></div></div>
          <div class="sect">Gender</div><div class="genders"></div>
          <div class="sect">Class</div><div class="grid"></div>
        </div>`),f=p.querySelector(".genders");for(const d of[!0,!1]){const h=d===l,m=n(`<button class="gbtn${h?" on":""}">
          <img src="${w}/ui/charselect/Login_Bt_Ico_${d?"Male":"Female"}_${h?"F":"N"}.png" alt=""></button>`);m.addEventListener("click",()=>t.pick(me(o,d))),f.append(m)}const i=p.querySelector(".grid");for(const d of fe){const h=d.spine===o,m=n(`<div class="tile${h?" on":""}">
            <img class="card" src="${u(d,h)}" alt="">
            <img class="emb" src="${w}/ui/charselect/Login_Ico_${d.icon}.png" alt="">
            <span class="nm">${d.name}</span>${d.soon?'<span class="soon">SOON</span>':""}</div>`);m.addEventListener("click",()=>t.pick(me(d.spine,l))),i.append(m)}return[p,n(`<div class="ch side" style="top:0;width:${b(g.panelW,1920)}">
              <h2>Class name</h2><p></p><p></p><p class="short"></p>
              <div class="lbl">Name</div><div class="fld"></div>
              <div class="lbl">Attributes</div><div class="stats">${"<i></i>".repeat(6)}</div>
              <div class="acts"><span class="btn gold">Create Character</span><span class="btn">Cancel</span></div>
            </div>`)]}},select:{label:"Select screen",get stage(){return g.selFull?{left:"0",top:"0",width:"100%",height:"100%"}:{left:b((1920-g.selW)/2,1920),top:"0",width:b(g.selW,1920),height:b(1080-g.selBottom,1080)}},chrome:t=>{const o=t.set,l=Ce[o.replace(/_[MF]$/,"")],u=o.endsWith("_F")?"F":"M",p=f=>`<img src="${l?`${C}/Login_Bt_Slot_${l}_${u}_${f===0?"F":"N"}.png`:`${C}/Login_Bt_SlotEmpty_00_N.png`}" onerror="this.src='${C}/Login_Bt_SlotEmpty_00_N.png'">`;return[n('<div class="ch cshead">Characters &mdash; demo</div>'),n(`<div class="ch details" style="bottom:${b(g.selBottom-170,1080)};width:${b(420,1920)};height:${b(96,1080)}">
              <b>Character</b><span>Class &middot; Level</span></div>`),n(`<div class="ch slots" style="bottom:${b(g.selBottom-420,1080)};height:${b(240,1080)}">${[0,1,2,3].map(p).join("")+"<i></i><i></i>"}</div>`),n(`<div class="ch csbtns" style="bottom:${b(g.selBottom-460,1080)}">
              <span class="btn gold">Enter World</span><span class="btn">Back</span></div>`)]}}},Te=1250,Q="l15.spineTuning",x={scale:1,offsetX:0,offsetY:0,smokeDrop:.25,smokeOpacity:1.9},k=t=>Object.fromEntries(Object.entries(t).map(([o,l])=>[o,{...l}]));function Ie(){const t={create:k(q.create),select:k(q.select),login:k(q.login)};try{const o=JSON.parse(localStorage.getItem(Q)??"{}");return o.create||o.select||o.login?{create:{...t.create,...k(o.create??{})},select:{...t.select,...k(o.select??{})},login:{...t.login,...k(o.login??{})}}:{create:{...t.create,...k(o)},select:t.select,login:t.login}}catch{return t}}function X(t){try{localStorage.setItem(Q,JSON.stringify(t))}catch{}}function Oe(t){const o={};for(const[l,u]of Object.entries(t)){const p={};for(const[f,i]of Object.entries(x)){const d=u[f];d!==void 0&&Math.abs(d-i)>1e-9&&(p[f]=Math.round(d*1e3)/1e3)}u.enabled!==void 0&&(p.enabled=u.enabled),Object.keys(p).length&&(o[l]=p)}return o}function V(t,o){const l=Object.entries(Oe(t)).map(([u,p])=>{const f=Object.entries(p).map(([i,d])=>`${i}: ${d}`).join(", ");return`${o}  ${u}: { ${f} },`});return l.length?l.join(`
`):`${o}  // nothing dialled - every set uses the automatic fit`}function Be(t){return["export const SPINE_TUNING: Record<TuningScreen, Record<string, SpineTuning>> = {"," create: {",V(t.create," ")," },"," select: {",V(t.select," ")," },"," login: {",V(t.login," ")," },","};"].join(`
`)}function Me(){const t=Ie();try{g={...E,...JSON.parse(localStorage.getItem(K)??"{}")}}catch{}const o=new URLSearchParams(location.search);let l=o.get("set")??ye[0],u=o.get("frame")??"create",p=null;const f=document.getElementById("app"),i=n('<div class="panel"></div>'),d=n('<div class="stagewrap"></div>'),h=n('<div class="screenbox"></div>'),m=n('<div class="stage"><div class="mid"></div><div class="base"></div></div>');h.append(m),d.append(h),f.append(i,d);let N=(o.get("chrome")??"1")!=="0";const B=n('<div class="canvashost"></div>');h.prepend(B);const S=()=>u==="select"?"select":u==="login"?"login":"create",$=()=>t[S()][l]??={...x},Se=e=>$()[e]??x[e],M=()=>{const e=d.clientWidth-36,s=d.clientHeight-36,c=Math.min(e,s*be);h.style.width=`${Math.round(c)}px`,h.style.height=`${Math.round(c/be)}px`,h.style.setProperty("--uiscale",String(c/1920));const a=J[u].stage;Object.assign(m.style,{left:a.left,top:a.top,width:a.width,height:a.height})};let R=()=>{},Z=()=>{};const T=()=>{for(const e of[...h.querySelectorAll(".ch")])e.remove();h.classList.toggle("bare",!N),N&&h.append(...J[u].chrome({set:l,pick:e=>R(e)}))},z=()=>{p?.destroy(),m.querySelector(".err")?.remove();const e=()=>{const a=m.getBoundingClientRect(),r=B.getBoundingClientRect();return{x:a.left-r.left,y:a.top-r.top,w:a.width,h:a.height}},s=S()==="login"?ge(l):void 0;p=s?he(B,w,s.layers[0].prefix,s.layers[0].name,"idle",a=>m.append(n(`<div class="err">${s.label}<br>${a.message}</div>`)),{layers:s.layers,boundsMode:"all",fill:1,introAnimation:"action",introInFit:!1,referenceAspect:16/9,cover:!0,tuning:$(),clearColor:[.03,.03,.04],region:e}):he(B,w,"CharSelect",l,"idle",a=>m.append(n(`<div class="err">${l}<br>${a.message}</div>`)),{unitHeight:Te,introAnimation:"action",tuning:$(),clearColor:[.055,.047,.07],backdrop:[`${C}/bg_base.jpg`,`${C}/bg_foreground.png`],region:e}),window.__tuneSpine=p;const c=window.setInterval(()=>{const a=p?.info();if(a){window.clearInterval(c),G.textContent=a.hazeSlots===0?`No ground haze in this set - these two do nothing here (${a.hazeRejected} effect slot(s) are the wrong shape for haze).`:`${a.hazeSlots} haze slot(s), ${a.hazeMoved} movable.`,G.classList.toggle("warn",a.hazeMoved===0),ve(A);for(const r of a.animations){const y=n(`<button class="anim">${r}</button>`);y.addEventListener("click",()=>{for(const v of A.querySelectorAll("button"))v.classList.remove("on");y.classList.add("on"),p?.play(r,!0)}),r==="idle"&&y.classList.add("on"),A.append(y)}}},120)};i.append(n("<h1>Spine framing tuner</h1>")),i.append(n('<p class="sub">Dial each set, then copy the table into spine-tuning.ts. Saved as you go. The two screens keep separate values.</p>'));const L=n("<select></select>"),ee=()=>{ve(L);for(const e of Y(S())){const s=S()==="login"?ge(e)?.label??e:e;L.append(n(`<option value="${e}">${s}</option>`))}L.value=l};ee();const P=n('<button title="Previous set">&lt;</button>'),D=n('<button title="Next set">&gt;</button>'),te=n('<div class="row"></div>');te.append(P,L,D),i.append(te);const I=n("<select></select>");for(const[e,s]of Object.entries(J))I.append(n(`<option value="${e}">${s.label}</option>`));I.value=u;const ne=n('<div class="row"></div>');ne.append(I),i.append(ne);const H=n(`<input type="checkbox" ${N?"checked":""}>`),se=n('<label class="row check"></label>');se.append(H,n("<span>Show that screen's UI around it</span>")),i.append(se),H.addEventListener("change",()=>{N=H.checked;const e=new URL(location.href);e.searchParams.set("chrome",N?"1":"0"),history.replaceState(null,"",e),T()}),i.append(n('<p class="hint">Values are relative, so a set dialled on one frame lands the same on the other.</p>'));const $e=[{k:"scale",label:"Scale",min:.1,max:8,step:.01},{k:"offsetY",label:"Move up",min:-3,max:3,step:.005},{k:"offsetX",label:"Move right",min:-3,max:3,step:.005},{k:"smokeDrop",label:"Haze drop",min:-5,max:5,step:.01},{k:"smokeOpacity",label:"Haze density",min:0,max:10,step:.05}],ae=new Map;for(const e of $e){const s=n(`<div class="sl"><label>${e.label}</label></div>`),c=n('<div class="ctl"></div>'),a=n(`<input type="range" min="${e.min}" max="${e.max}" step="${e.step}">`),r=n(`<input type="number" class="num" step="${e.step}">`);c.append(a,r),s.append(c),i.append(s),ae.set(e.k,{range:a,num:r});const y=v=>{$()[e.k]=v,r.value=String(Math.round(v*1e3)/1e3),a.value=String(v),r.classList.toggle("dirty",Math.abs(v-x[e.k])>1e-9),p?.setTuning($()),X(t),_()};a.addEventListener("input",()=>y(Number(a.value))),r.addEventListener("input",()=>{r.value.trim()!==""&&Number.isFinite(Number(r.value))&&y(Number(r.value))})}const U=n('<div class="row enrow"></div>'),F=n('<input type="checkbox">'),oe=n("<label></label>");oe.append(F,document.createTextNode(" Enabled — include this backdrop in the login screen’s random pool")),U.append(oe),i.append(U),F.addEventListener("change",()=>{$().enabled=F.checked,X(t),Z()});const G=n('<p class="hint haze"></p>');i.append(G),i.append(n('<p class="grp">Animation</p>'));const A=n('<div class="row anims"></div>');i.append(A),i.append(n('<p class="grp">Screen layout</p>'));const we=[{k:"rosterW",label:"Class panel width",min:0,max:900,only:"create"},{k:"panelW",label:"Info panel width",min:0,max:1e3,only:"create"},{k:"selW",label:"Stage width",min:200,max:1920,only:"select"},{k:"selBottom",label:"Stage bottom gap",min:0,max:1e3,only:"select"},{k:"selFull",label:"Full-window stage (0/1)",min:0,max:1,only:"select"}],le=new Map;for(const e of we){const s=n(`<div class="sl"><label>${e.label}</label></div>`),c=n('<div class="ctl"></div>'),a=n(`<input type="range" min="${e.min}" max="${e.max}" step="1">`),r=n('<input type="number" class="num" step="1">');c.append(a,r),s.append(c),i.append(s),le.set(e.k,{wrap:s,range:a,num:r,only:e.only});const y=v=>{g[e.k]=v,r.value=String(v),a.value=String(v),r.classList.toggle("dirty",v!==E[e.k]);try{localStorage.setItem(K,JSON.stringify(g))}catch{}M(),T(),_()};a.addEventListener("input",()=>y(Number(a.value))),r.addEventListener("input",()=>{r.value.trim()!==""&&Number.isFinite(Number(r.value))&&y(Number(r.value))})}const ie=()=>{for(const[e,s]of le)s.range.value=String(g[e]),s.num.value=String(g[e]),s.num.classList.toggle("dirty",g[e]!==E[e]),s.wrap.style.display=!s.only||s.only===u?"":"none"},ce=n("<button>Reset this set</button>"),O=n('<button class="primary">Copy table</button>'),re=n('<div class="row"></div>');re.append(ce,O),i.append(re);const de=n("<button>Discard local edits, reload from code</button>");de.addEventListener("click",()=>{if(confirm("Discard every value dialled in this browser and reload the tables from spine-tuning.ts?")){try{localStorage.removeItem(Q),localStorage.removeItem(K)}catch{}location.reload()}});const pe=n('<div class="row"></div>');pe.append(de),i.append(pe);const W=n("<textarea readonly></textarea>");i.append(n('<p class="hint" style="margin-bottom:0">Paste this into spine-tuning.ts:</p>'),W);const _=()=>{const e=Object.keys(E).filter(c=>g[c]!==E[c]),s=e.length?`

// screen layout (reference px at 1920x1080)
${e.map(c=>`// ${c}: ${g[c]}  (was ${E[c]})`).join(`
`)}`:"";W.value=Be(t)+s};Z=()=>_();const j=()=>{for(const[e,{range:s,num:c}]of ae){const a=Se(e);s.value=String(a),c.value=String(Math.round(a*1e3)/1e3),c.classList.toggle("dirty",Math.abs(a-x[e])>1e-9)}U.style.display=S()==="login"?"":"none",F.checked=$().enabled===!0};R=e=>{l=e,L.value=e;const s=new URL(location.href);s.searchParams.set("set",e),history.replaceState(null,"",s),j(),T(),z()},L.addEventListener("change",()=>R(L.value));const ue=e=>{const s=Y(S());R(s[(s.indexOf(l)+e+s.length)%s.length])};P.addEventListener("click",()=>ue(-1)),D.addEventListener("click",()=>ue(1)),I.addEventListener("change",()=>{u=I.value;const e=new URL(location.href);e.searchParams.set("frame",u),history.replaceState(null,"",e);const s=Y(S()),c=!s.includes(l);c&&(l=s[0]),ee(),M(),T(),ie(),j(),_(),c||S()==="login"?z():p?.setTuning($())}),ce.addEventListener("click",()=>{t[S()][l]={...x},X(t),j(),_(),p?.setTuning($())}),O.addEventListener("click",()=>{W.select(),navigator.clipboard?.writeText(W.value).then(()=>{O.textContent="Copied",setTimeout(()=>O.textContent="Copy table",1200)},()=>{O.textContent="Select + copy"})}),document.addEventListener("keydown",e=>{e.target instanceof HTMLInputElement||e.target instanceof HTMLSelectElement||(e.key==="ArrowRight"&&D.click(),e.key==="ArrowLeft"&&P.click())}),window.addEventListener("resize",M),M(),T(),j(),ie(),_(),z()}(async()=>{const t=await Le();w=t.assetBase,C=`${t.assetBase}/ui/charselect`,Me()})();
