diff --git a/dist/BlueMarble.user.css b/dist/BlueMarble.user.css index e873a30..61adbd7 100644 --- a/dist/BlueMarble.user.css +++ b/dist/BlueMarble.user.css @@ -1 +1 @@ -#bm-l{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000;transition:all .3s ease;max-width:300px;width:auto}#bm-4,#bm-l hr,#bm-3,#bm-1{transition:opacity .2s ease,height .2s ease}div#bm-l{font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}#bm-g{margin-bottom:.5em;background:url('data:image/svg+xml;utf8,') repeat;cursor:grab;width:100%;height:1em}#bm-g.dragging{cursor:grabbing}#bm-7{margin-bottom:.5em}#bm-7[style*="text-align: center"]{display:flex;flex-direction:column;align-items:center;justify-content:center}#bm-l[style*="padding: 5px"]{width:auto!important;max-width:300px;min-width:200px}#bm-l img{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle;transition:opacity .2s ease}#bm-7[style*="text-align: center"] img{display:block;margin:0 auto}#bm-g{transition:margin-bottom .2s ease}#bm-l h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}#bm-3 input[type=checkbox]{vertical-align:middle;margin-right:.5ch}#bm-3 label{margin-right:.5ch}.bm-p{border:white 1px solid;height:1.5em;width:1.5em;margin-top:2px;text-align:center;line-height:1em;padding:0!important}#bm-c{vertical-align:middle}#bm-c svg{width:50%;margin:0 auto;fill:#111}div:has(>#bm-button-teleport){display:flex;gap:.5ch}#bm-button-favorite svg,#bm-button-template svg{height:1em;margin:2px auto 0;text-align:center;line-height:1em;vertical-align:bottom}#bm-8 input[type=number]{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}#bm-8 input[type=number]::-webkit-outer-spin-button,#bm-8 input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}#bm-0{display:flex;flex-direction:row;flex-wrap:wrap;align-content:center;justify-content:center;align-items:center;gap:1ch}div:has(>#bm-2)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#bm-a{font-size:small;background-color:#0003;padding:0 .5ch;height:3.75em;width:100%}#bm-1{display:flex;justify-content:space-between}#bm-l small{font-size:x-small;color:#d3d3d3}#bm-4,#bm-3,#bm-8,#bm-0,div:has(>#bm-2),#bm-a{margin-top:.5em}#bm-l button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}#bm-l button:hover,#bm-l button:focus-visible{background-color:#1061e5}#bm-l button:active,#bm-l button:disabled{background-color:#2e97ff}#bm-l button:disabled{text-decoration:line-through} +#bm-l{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000;transition:all .3s ease;max-width:300px;width:auto;will-change:transform;backface-visibility:hidden;-webkit-backface-visibility:hidden;transform-style:preserve-3d;-webkit-transform-style:preserve-3d}#bm-4,#bm-l hr,#bm-3,#bm-1{transition:opacity .2s ease,height .2s ease}div#bm-l{font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}#bm-g{margin-bottom:.5em;background:url('data:image/svg+xml;utf8,') repeat;cursor:grab;width:100%;height:1em}#bm-g.dragging{cursor:grabbing}#bm-l:has(#bm-g.dragging){pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}#bm-g.dragging{pointer-events:auto}#bm-7{margin-bottom:.5em}#bm-7[style*="text-align: center"]{display:flex;flex-direction:column;align-items:center;justify-content:center}#bm-l[style*="padding: 5px"]{width:auto!important;max-width:300px;min-width:200px}#bm-l img{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle;transition:opacity .2s ease}#bm-7[style*="text-align: center"] img{display:block;margin:0 auto}#bm-g{transition:margin-bottom .2s ease}#bm-l h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}#bm-3 input[type=checkbox]{vertical-align:middle;margin-right:.5ch}#bm-3 label{margin-right:.5ch}.bm-p{border:white 1px solid;height:1.5em;width:1.5em;margin-top:2px;text-align:center;line-height:1em;padding:0!important}#bm-c{vertical-align:middle}#bm-c svg{width:50%;margin:0 auto;fill:#111}div:has(>#bm-button-teleport){display:flex;gap:.5ch}#bm-button-favorite svg,#bm-button-template svg{height:1em;margin:2px auto 0;text-align:center;line-height:1em;vertical-align:bottom}#bm-8 input[type=number]{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}#bm-8 input[type=number]::-webkit-outer-spin-button,#bm-8 input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}#bm-0{display:flex;flex-direction:row;flex-wrap:wrap;align-content:center;justify-content:center;align-items:center;gap:1ch}div:has(>#bm-2)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#bm-2,input[type=file][id*=template]{display:none!important;visibility:hidden!important;position:absolute!important;left:-9999px!important;top:-9999px!important;width:0!important;height:0!important;opacity:0!important;z-index:-9999!important;pointer-events:none!important}#bm-a{font-size:small;background-color:#0003;padding:0 .5ch;height:3.75em;width:100%}#bm-1{display:flex;justify-content:space-between}#bm-l small{font-size:x-small;color:#d3d3d3}#bm-4,#bm-3,#bm-8,#bm-0,div:has(>#bm-2),#bm-a{margin-top:.5em}#bm-l button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}#bm-l button:hover,#bm-l button:focus-visible{background-color:#1061e5}#bm-l button:active,#bm-l button:disabled{background-color:#2e97ff}#bm-l button:disabled{text-decoration:line-through} diff --git a/dist/BlueMarble.user.js b/dist/BlueMarble.user.js index 1788f81..cfb2b60 100644 --- a/dist/BlueMarble.user.js +++ b/dist/BlueMarble.user.js @@ -1,23 +1,23 @@ // ==UserScript== // @name Blue Marble // @namespace https://github.com/SwingTheVine/ -// @version 0.71.0 +// @version 0.72.0 // @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. // @author SwingTheVine // @license MPL-2.0 // @supportURL https://discord.gg/tpeBPy46hf // @homepageURL https://github.com/SwingTheVine/Wplace-BlueMarble -// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/a3ff8840069a733d4cf4c27b8336beddf3706b60/dist/assets/Favicon.png +// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/6b6a45cba9943e5960c9887654aa704a90c02636/dist/assets/Favicon.png // @updateURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble.user.js // @downloadURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble.user.js // @run-at document-start // @match *://*.wplace.live/* // @grant GM_getResourceText // @grant GM_addStyle -// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/a3ff8840069a733d4cf4c27b8336beddf3706b60/dist/BlueMarble.user.css +// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/6b6a45cba9943e5960c9887654aa704a90c02636/dist/BlueMarble.user.css // ==/UserScript== // Wplace --> https://wplace.live // License --> https://www.mozilla.org/en-US/MPL/2.0/ -(()=>{var t,e,n=t=>{throw TypeError(t)},i=(t,e,i)=>e.has(t)?n("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,i),s=(t,e,i)=>(((t,e)=>{e.has(t)||n("Cannot access private method")})(t,e),i);t=new WeakSet,e=function(t,e={},n={}){const i=document.createElement(t);this.t?(this.i.appendChild(i),this.o.push(this.i),this.i=i):(this.t=i,this.i=i);for(const[t,n]of Object.entries(e))i[t]=n;for(const[t,e]of Object.entries(n))i[t]=e;return i};var o,a=class{constructor({displayName:t="My template",l:e=0,h:n="",url:i="",file:s=null,coords:o=null,u:a=null,m:r=1e3}={}){this.displayName=t,this.l=e,this.h=n,this.url=i,this.file=s,this.coords=o,this.u=a,this.m=r,this.p=0}async v(){const t=await createImageBitmap(this.file),e=t.width,n=t.height,i=e*n;this.p=i;const s={},o=new OffscreenCanvas(this.m,this.m),a=o.getContext("2d",{M:!0});for(let i=this.coords[3];i0;)n=e[t%i]+n,t=Math.floor(t/i);return n}o=new WeakSet;var c=GM_info.script.name.toString(),l=GM_info.script.version.toString();!function(t){const e=document.createElement("script");e.setAttribute("bm-o",c),e.setAttribute("bm-m","color: cornflowerblue;"),e.textContent=`(${t})();`,document.documentElement.appendChild(e),e.remove()}(()=>{const t=document.currentScript,e=t?.getAttribute("bm-o")||"Blue Marble",n=t?.getAttribute("bm-m")||"",i=new Map;window.addEventListener("message",t=>{const{source:s,endpoint:o,blobID:a,blobData:r,blink:c}=t.data;if(Date.now(),"blue-marble"==s&&a&&r&&!o){const t=i.get(a);"function"==typeof t?t(r):function(...t){(0,console.warn)(...t)}(`%c${e}%c: Attempted to retrieve a blob (%s) from queue, but the blobID was not a function! Skipping...`,n,"",a),i.delete(a)}});const s=window.fetch;window.fetch=async function(...t){const e=await s.apply(this,t),n=e.clone(),o=(t[0]instanceof Request?t[0]?.url:t[0])||"ignore",a=n.headers.get("content-type")||"";if(a.includes("application/json"))n.json().then(t=>{window.postMessage({source:"blue-marble",endpoint:o,jsonData:t},"*")}).catch(t=>{});else if(a.includes("image/")&&!o.includes("openfreemap")){const t=Date.now(),e=await n.blob();return new Promise(s=>{const a=crypto.randomUUID();i.set(a,t=>{s(new Response(t,{headers:n.headers,status:n.status,statusText:n.statusText}))}),window.postMessage({source:"blue-marble",endpoint:o,blobID:a,blobData:e,blink:t})}).catch(t=>{Date.now()})}return e}});var h=GM_getResourceText("CSS-BM-File");GM_addStyle(h);var u=document.createElement("link");u.href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap",u.rel="preload",u.as="style",u.onload=function(){this.onload=null,this.rel="stylesheet"},document.head.appendChild(u),new class{constructor(){this.$=null,this.C=null,this.D="#bm-5"}I(t){return this.C=t,this.$=new MutationObserver(t=>{for(const e of t)for(const t of e.addedNodes)t instanceof HTMLElement&&t.matches?.(this.D)}),this}k(){return this.$}observe(t,e=!1,n=!1){t.observe(this.C,{childList:e,subtree:n})}};var m=new class{constructor(e,n){i(this,t),this.name=e,this.version=n,this.T=null,this.N="bm-a",this.t=null,this.i=null,this.o=[]}S(t){this.T=t}B(){return this.o.length>0&&(this.i=this.o.pop()),this}O(t){t.appendChild(this.t),this.t=null,this.i=null,this.o=[]}L(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"div",{},n)),this}H(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"p",{},n)),this}j(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"small",{},n)),this}q(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"img",{},n)),this}P(n,i={},o=()=>{}){return o(this,s(this,t,e).call(this,"h"+n,{},i)),this}R(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"hr",{},n)),this}_(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"br",{},n)),this}V(n={},i=()=>{}){const o=s(this,t,e).call(this,"label",{textContent:n.textContent??""});delete n.textContent;const a=s(this,t,e).call(this,"input",{type:"checkbox"},n);return o.insertBefore(a,o.firstChild),this.B(),i(this,o,a),this}Y(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"button",{},n)),this}F(n={},i=()=>{}){const o=n.title??n.textContent??"Help: No info";delete n.textContent,n.title=`Help: ${o}`;const a={textContent:"?",className:"bm-p",onclick:()=>{this.G(this.N,o)}};return i(this,s(this,t,e).call(this,"button",a,n)),this}U(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"input",{},n)),this}W(n={},i=()=>{}){const o=n.textContent??"";delete n.textContent;const a=s(this,t,e).call(this,"div"),r=s(this,t,e).call(this,"input",{type:"file",style:"display: none;"},n);this.B();const c=s(this,t,e).call(this,"button",{textContent:o});return this.B(),this.B(),c.addEventListener("click",()=>{r.click()}),r.addEventListener("change",()=>{c.style.maxWidth=`${c.offsetWidth}px`,r.files.length>0?c.textContent=r.files[0].name:c.textContent=o}),i(this,a,r,c),this}X(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"textarea",{},n)),this}G(t,e,n=!1){const i=document.getElementById(t.replace(/^#/,""));i&&(i instanceof HTMLInputElement?i.value=e:n?i.textContent=e:i.innerHTML=e)}A(t,e){let n,i=!1,s=0;t=document.querySelector("#"==t?.[0]?t:"#"+t),e=document.querySelector("#"==e?.[0]?e:"#"+e),t&&e?(e.addEventListener("mousedown",function(o){i=!0,n=o.clientX-t.getBoundingClientRect().left,s=o.clientY-t.getBoundingClientRect().top,document.body.style.userSelect="none",e.classList.add("dragging")}),e.addEventListener("touchstart",function(o){i=!0;const a=o?.touches?.[0];a&&(n=a.clientX-t.getBoundingClientRect().left,s=a.clientY-t.getBoundingClientRect().top,document.body.style.userSelect="none",e.classList.add("dragging"))},{passive:!1}),document.addEventListener("mousemove",function(e){i&&(t.style.left=e.clientX-n+"px",t.style.top=e.clientY-s+"px",t.style.right="")}),document.addEventListener("touchmove",function(e){if(i){const i=e?.touches?.[0];if(!i)return;t.style.left=i.clientX-n+"px",t.style.top=i.clientY-s+"px",t.style.right="",e.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",function(){i=!1,document.body.style.userSelect="",e.classList.remove("dragging")}),document.addEventListener("touchend",function(){i=!1,document.body.style.userSelect="",e.classList.remove("dragging")}),document.addEventListener("touchcancel",function(){i=!1,document.body.style.userSelect="",e.classList.remove("dragging")})):this.J(`Can not drag! ${t?"":"moveMe"} ${t||e?"":"and "}${e?"":"iMoveThings "}was not found!`)}Z(t){(0,console.info)(`${this.name}: ${t}`),this.G(this.N,"Status: "+t,!0)}J(t){(0,console.error)(`${this.name}: ${t}`),this.G(this.N,"Error: "+t,!0)}}(c,l),d=new class{constructor(t,e,n){i(this,o),this.name=t,this.version=e,this.t=n,this.K="1.0.0",this.tt=null,this.et="!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",this.m=1e3,this.nt=3,this.it=null,this.st=null,this.ot="bm-n",this.rt="div#map canvas.maplibregl-canvas",this.ct=null,this.lt="",this.ht=[],this.ut=null}dt(){if(document.body.contains(this.it))return this.it;document.getElementById(this.ot)?.remove();const t=document.querySelector(this.rt),e=document.createElement("canvas");return e.id=this.ot,e.className="maplibregl-canvas",e.style.position="absolute",e.style.top="0",e.style.left="0",e.style.height=t?.clientHeight*(window.devicePixelRatio||1)+"px",e.style.width=t?.clientWidth*(window.devicePixelRatio||1)+"px",e.height=t?.clientHeight*(window.devicePixelRatio||1),e.width=t?.clientWidth*(window.devicePixelRatio||1),e.style.zIndex="8999",e.style.pointerEvents="none",t?.parentElement?.appendChild(e),this.it=e,window.addEventListener("move",this.bt),window.addEventListener("zoom",this.ft),window.addEventListener("resize",this.wt),this.it}async gt(){return{whoami:this.name.replace(" ",""),scriptVersion:this.version,schemaVersion:this.K,templates:{}}}async vt(t,e,n){this.ut||(this.ut=await this.gt()),this.t.Z(`Creating template at ${n.join(", ")}...`);const i=new a({displayName:e,l:0,h:r(this.tt||0,this.et),file:t,coords:n});i.u=await i.v(this.m),this.ut.templates[`${i.l} ${i.h}`]={name:i.displayName,enabled:!0,tiles:i.u},this.ht=[],this.ht.push(i);const s=(new Intl.NumberFormat).format(i.p);this.t.Z(`Template created at ${n.join(", ")}! Total pixels: ${s}`)}xt(){}async yt(){this.ut||(this.ut=await this.gt())}async Mt(t,e){const n=this.m*this.nt;e=e[0].toString().padStart(4,"0")+","+e[1].toString().padStart(4,"0");const i=this.ht;i.sort((t,e)=>t.l-e.l);const s=i.map(t=>{const n=Object.keys(t.u).filter(t=>t.startsWith(e));if(0===n.length)return null;const i=n.map(e=>t.u[e]);return i?.[0]}).filter(Boolean);if(s.length>0){const t=i.filter(t=>Object.keys(t.u).filter(t=>t.startsWith(e)).length>0).reduce((t,e)=>t+(e.p||0),0),n=(new Intl.NumberFormat).format(t);this.t.Z(`Displaying ${s.length} template${1==s.length?"":"s"}. Total pixels: ${n}`)}const o=await createImageBitmap(t),a=new OffscreenCanvas(n,n),r=a.getContext("2d");r.imageSmoothingEnabled=!1,r.beginPath(),r.rect(0,0,n,n),r.clip(),r.clearRect(0,0,n,n),r.drawImage(o,0,0,n,n);for(const t of s)r.drawImage(t,0,0);return await a.convertToBlob({type:"image/png"})}$t(){}}(c,l,m),p=new class{constructor(t){this.Ct=t,this.Dt=!1,this.It=[],this.kt=[]}Tt(t){window.addEventListener("message",async e=>{const n=e.data,i=n.jsonData;if(!n||"blue-marble"!==n.source)return;if(!n.endpoint)return;const s=n.endpoint?.split("?")[0].split("/").filter(t=>t&&isNaN(Number(t))).filter(t=>t&&!t.includes(".")).pop();switch(s){case"me":if(i.status&&"2"!=i.status?.toString()[0])return void t.J("You are not logged in!\nCould not fetch userdata.");const e=Math.ceil(Math.pow(Math.floor(i.level)*Math.pow(30,.65),1/.65)-i.pixelsPainted);i.id||i.id,this.Ct.tt=i.id,t.G("bm-f",`Username: ${function(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}(i.name)}`),t.G("bm-b",`Droplets: ${(new Intl.NumberFormat).format(i.droplets)}`),t.G("bm-6",`Next level in ${(new Intl.NumberFormat).format(e)} pixel${1==e?"":"s"}`);break;case"pixel":const s=n.endpoint.split("?")[0].split("/").filter(t=>t&&!isNaN(Number(t))),r=new URLSearchParams(n.endpoint.split("?")[1]),c=[r.get("x"),r.get("y")];if(this.It.length&&(!s.length||!c.length))return void t.J("Coordinates are malformed!\nDid you try clicking the canvas first?");this.It=[...s,...c];const l=(o=s,a=c,[parseInt(o[0])%4*1e3+parseInt(a[0]),parseInt(o[1])%4*1e3+parseInt(a[1])]),h=document.querySelectorAll("span");for(const t of h)if(t.textContent.trim().includes(`${l[0]}, ${l[1]}`)){let e=document.querySelector("#bm-5");const n=`(Tl X: ${s[0]}, Tl Y: ${s[1]}, Px X: ${c[0]}, Px Y: ${c[1]})`;e?e.textContent=n:(e=document.createElement("span"),e.id="bm-5",e.textContent=n,e.style="margin-left: calc(var(--spacing)*3); font-size: small;",t.parentNode.parentNode.parentNode.insertAdjacentElement("afterend",e))}break;case"tiles":let u=n.endpoint.split("/");u=[parseInt(u[u.length-2]),parseInt(u[u.length-1].replace(".png",""))];const m=n.blobID,d=n.blobData,p=await this.Ct.Mt(d,u);window.postMessage({source:"blue-marble",blobID:m,blobData:p,blink:n.blink});break;case"robots":this.Dt="false"==i.userscript?.toString().toLowerCase()}var o,a})}}(d);m.S(p),function(){let t=!1;m.L({id:"bm-l",style:"top: 10px; right: 75px;"}).L({id:"bm-7"}).L({id:"bm-g"}).B().q({alt:"Blue Marble Icon - Click to minimize/maximize",src:"https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png",style:"cursor: pointer;"},(e,n)=>{n.addEventListener("click",()=>{t=!t;const i=document.querySelector("#bm-l"),s=document.querySelector("#bm-7"),o=document.querySelector("#bm-g"),a=document.querySelector("#bm-8"),r=document.querySelector("#bm-c"),c=document.querySelector("#bm-d"),l=document.querySelectorAll("#bm-8 input");t||(i.style.width="auto",i.style.maxWidth="300px",i.style.minWidth="200px",i.style.padding="10px"),["#bm-l h1","#bm-4","#bm-l hr","#bm-3 > *:not(#bm-8)","#bm-2","#bm-1",`#${e.N}`].forEach(e=>{document.querySelectorAll(e).forEach(e=>{e.style.display=t?"none":""})}),t?(a&&(a.style.display="none"),r&&(r.style.display="none"),c&&(c.style.display="none"),l.forEach(t=>{t.style.display="none"}),i.style.width="60px",i.style.height="76px",i.style.maxWidth="60px",i.style.minWidth="60px",i.style.padding="8px",n.style.marginLeft="3px",s.style.textAlign="center",s.style.margin="0",s.style.marginBottom="0",o&&(o.style.display="",o.style.marginBottom="0.25em")):(a&&(a.style.display="",a.style.flexDirection="",a.style.justifyContent="",a.style.alignItems="",a.style.gap="",a.style.textAlign="",a.style.margin=""),r&&(r.style.display=""),c&&(c.style.display="",c.style.marginTop=""),l.forEach(t=>{t.style.display=""}),n.style.marginLeft="",i.style.padding="10px",s.style.textAlign="",s.style.margin="",s.style.marginBottom="",o&&(o.style.marginBottom="0.5em"),i.style.width="",i.style.height=""),n.alt=t?"Blue Marble Icon - Minimized (Click to maximize)":"Blue Marble Icon - Maximized (Click to minimize)"})}).B().P(1,{textContent:c}).B().B().R().B().L({id:"bm-4"}).H({id:"bm-f",textContent:"Username:"}).B().H({id:"bm-b",textContent:"Droplets:"}).B().H({id:"bm-6",textContent:"Next level in..."}).B().B().R().B().L({id:"bm-3"}).L({id:"bm-8"}).Y({id:"bm-c",className:"bm-p",style:"margin-top: 0;",innerHTML:''},(t,e)=>{e.onclick=()=>{const e=t.T?.It;e?.[0]?(t.G("bm-h",e?.[0]||""),t.G("bm-i",e?.[1]||""),t.G("bm-j",e?.[2]||""),t.G("bm-k",e?.[3]||"")):t.J("Coordinates are malformed! Did you try clicking on the canvas first?")}}).B().U({type:"number",id:"bm-h",placeholder:"Tl X",min:0,max:2047,step:1,required:!0}).B().U({type:"number",id:"bm-i",placeholder:"Tl Y",min:0,max:2047,step:1,required:!0}).B().U({type:"number",id:"bm-j",placeholder:"Px X",min:0,max:2047,step:1,required:!0}).B().U({type:"number",id:"bm-k",placeholder:"Px Y",min:0,max:2047,step:1,required:!0}).B().B().W({id:"bm-2",textContent:"Upload Template",accept:"image/png, image/jpeg, image/webp, image/bmp, image/gif"}).B().L({id:"bm-0"}).Y({id:"bm-d",textContent:"Enable"},(t,e)=>{e.onclick=()=>{const e=document.querySelector("#bm-2"),n=document.querySelector("#bm-h");if(!n.checkValidity())return n.reportValidity(),void t.J("Coordinates are malformed! Did you try clicking on the canvas first?");const i=document.querySelector("#bm-i");if(!i.checkValidity())return i.reportValidity(),void t.J("Coordinates are malformed! Did you try clicking on the canvas first?");const s=document.querySelector("#bm-j");if(!s.checkValidity())return s.reportValidity(),void t.J("Coordinates are malformed! Did you try clicking on the canvas first?");const o=document.querySelector("#bm-k");if(!o.checkValidity())return o.reportValidity(),void t.J("Coordinates are malformed! Did you try clicking on the canvas first?");e?.files[0]?(d.vt(e.files[0],e.files[0]?.name.replace(/\.[^/.]+$/,""),[Number(n.value),Number(i.value),Number(s.value),Number(o.value)]),t.Z("Drew to canvas!")):t.J("No file selected!")}}).B().B().X({id:m.N,placeholder:`Status: Sleeping...\nVersion: ${l}`,readOnly:!0}).B().L({id:"bm-1"}).L().Y({id:"bm-9",className:"bm-p",innerHTML:"🎨",title:"Template Color Converter"},(t,e)=>{e.addEventListener("click",()=>{window.open("https://pepoafonso.github.io/color_converter_wplace/","_blank","noopener noreferrer")})}).B().B().j({textContent:"Made by SwingTheVine",style:"margin-top: auto;"}).B().B().B().O(document.body)}(),m.A("#bm-l","#bm-g"),p.Tt(m),new MutationObserver((t,e)=>{const n=document.querySelector("#color-1");if(!n)return;let i=document.querySelector("#bm-e");i||(i=document.createElement("button"),i.id="bm-e",i.textContent="Move ↑",i.className="btn btn-soft",i.onclick=function(){const t=this.parentNode.parentNode.parentNode.parentNode,e="Move ↑"==this.textContent;t.parentNode.className=t.parentNode.className.replace(e?"bottom":"top",e?"top":"bottom"),t.style.borderTopLeftRadius=e?"0px":"var(--radius-box)",t.style.borderTopRightRadius=e?"0px":"var(--radius-box)",t.style.borderBottomLeftRadius=e?"var(--radius-box)":"0px",t.style.borderBottomRightRadius=e?"var(--radius-box)":"0px",this.textContent=e?"Move ↓":"Move ↑"},n.parentNode.parentNode.parentNode.parentNode.querySelector("h2").parentNode.appendChild(i))}).observe(document.body,{childList:!0,subtree:!0}),function(...t){(0,console.log)(...t)}(`%c${c}%c (${l}) userscript has loaded!`,"color: cornflowerblue;","")})(); +(()=>{var t,e,n=t=>{throw TypeError(t)},i=(t,e,i)=>e.has(t)?n("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,i),s=(t,e,i)=>(((t,e)=>{e.has(t)||n("Cannot access private method")})(t,e),i);t=new WeakSet,e=function(t,e={},n={}){const i=document.createElement(t);this.t?(this.i.appendChild(i),this.o.push(this.i),this.i=i):(this.t=i,this.i=i);for(const[t,n]of Object.entries(e))i[t]=n;for(const[t,e]of Object.entries(n))i[t]=e;return i};var o,a=class{constructor({displayName:t="My template",l:e=0,h:n="",url:i="",file:s=null,coords:o=null,u:a=null,m:r=1e3}={}){this.displayName=t,this.l=e,this.h=n,this.url=i,this.file=s,this.coords=o,this.u=a,this.m=r,this.p=0}async v(){const t=await createImageBitmap(this.file),e=t.width,n=t.height,i=e*n;this.p=i;const s={},o=new OffscreenCanvas(this.m,this.m),a=o.getContext("2d",{M:!0});for(let i=this.coords[3];i0;)n=e[t%i]+n,t=Math.floor(t/i);return n}o=new WeakSet;var c=GM_info.script.name.toString(),l=GM_info.script.version.toString();!function(t){const e=document.createElement("script");e.setAttribute("bm-o",c),e.setAttribute("bm-m","color: cornflowerblue;"),e.textContent=`(${t})();`,document.documentElement.appendChild(e),e.remove()}(()=>{const t=document.currentScript,e=t?.getAttribute("bm-o")||"Blue Marble",n=t?.getAttribute("bm-m")||"",i=new Map;window.addEventListener("message",t=>{const{source:s,endpoint:o,blobID:a,blobData:r,blink:c}=t.data;if(Date.now(),"blue-marble"==s&&a&&r&&!o){const t=i.get(a);"function"==typeof t?t(r):function(...t){(0,console.warn)(...t)}(`%c${e}%c: Attempted to retrieve a blob (%s) from queue, but the blobID was not a function! Skipping...`,n,"",a),i.delete(a)}});const s=window.fetch;window.fetch=async function(...t){const e=await s.apply(this,t),n=e.clone(),o=(t[0]instanceof Request?t[0]?.url:t[0])||"ignore",a=n.headers.get("content-type")||"";if(a.includes("application/json"))n.json().then(t=>{window.postMessage({source:"blue-marble",endpoint:o,jsonData:t},"*")}).catch(t=>{});else if(a.includes("image/")&&!o.includes("openfreemap")){const t=Date.now(),e=await n.blob();return new Promise(s=>{const a=crypto.randomUUID();i.set(a,t=>{s(new Response(t,{headers:n.headers,status:n.status,statusText:n.statusText}))}),window.postMessage({source:"blue-marble",endpoint:o,blobID:a,blobData:e,blink:t})}).catch(t=>{Date.now()})}return e}});var h=GM_getResourceText("CSS-BM-File");GM_addStyle(h);var u=document.createElement("link");u.href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap",u.rel="preload",u.as="style",u.onload=function(){this.onload=null,this.rel="stylesheet"},document.head.appendChild(u),new class{constructor(){this.$=null,this.C=null,this.D="#bm-5"}I(t){return this.C=t,this.$=new MutationObserver(t=>{for(const e of t)for(const t of e.addedNodes)t instanceof HTMLElement&&t.matches?.(this.D)}),this}k(){return this.$}observe(t,e=!1,n=!1){t.observe(this.C,{childList:e,subtree:n})}};var m=new class{constructor(e,n){i(this,t),this.name=e,this.version=n,this.T=null,this.N="bm-a",this.t=null,this.i=null,this.o=[]}S(t){this.T=t}B(){return this.o.length>0&&(this.i=this.o.pop()),this}O(t){t.appendChild(this.t),this.t=null,this.i=null,this.o=[]}L(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"div",{},n)),this}H(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"p",{},n)),this}q(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"small",{},n)),this}j(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"img",{},n)),this}F(n,i={},o=()=>{}){return o(this,s(this,t,e).call(this,"h"+n,{},i)),this}P(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"hr",{},n)),this}R(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"br",{},n)),this}_(n={},i=()=>{}){const o=s(this,t,e).call(this,"label",{textContent:n.textContent??""});delete n.textContent;const a=s(this,t,e).call(this,"input",{type:"checkbox"},n);return o.insertBefore(a,o.firstChild),this.B(),i(this,o,a),this}A(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"button",{},n)),this}V(n={},i=()=>{}){const o=n.title??n.textContent??"Help: No info";delete n.textContent,n.title=`Help: ${o}`;const a={textContent:"?",className:"bm-p",onclick:()=>{this.Y(this.N,o)}};return i(this,s(this,t,e).call(this,"button",a,n)),this}G(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"input",{},n)),this}U(n={},i=()=>{}){const o=n.textContent??"";delete n.textContent;const a=s(this,t,e).call(this,"div"),r=s(this,t,e).call(this,"input",{type:"file",style:"display: none !important; visibility: hidden !important; position: absolute !important; left: -9999px !important; width: 0 !important; height: 0 !important; opacity: 0 !important;"},n);this.B();const c=s(this,t,e).call(this,"button",{textContent:o});return this.B(),this.B(),r.setAttribute("tabindex","-1"),r.setAttribute("aria-hidden","true"),c.addEventListener("click",()=>{r.click()}),r.addEventListener("change",()=>{c.style.maxWidth=`${c.offsetWidth}px`,r.files.length>0?c.textContent=r.files[0].name:c.textContent=o}),i(this,a,r,c),this}W(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"textarea",{},n)),this}Y(t,e,n=!1){const i=document.getElementById(t.replace(/^#/,""));i&&(i instanceof HTMLInputElement?i.value=e:n?i.textContent=e:i.innerHTML=e)}X(t,e){let n,i=!1,s=0,o=null,a=0,r=0,c=0,l=0;if(t=document.querySelector("#"==t?.[0]?t:"#"+t),e=document.querySelector("#"==e?.[0]?e:"#"+e),!t||!e)return void this.J(`Can not drag! ${t?"":"moveMe"} ${t||e?"":"and "}${e?"":"iMoveThings "}was not found!`);const h=()=>{if(i){const e=Math.abs(a-c),n=Math.abs(r-l);(e>.5||n>.5)&&(a=c,r=l,t.style.transform=`translate(${a}px, ${r}px)`,t.style.left="0px",t.style.top="0px",t.style.right=""),o=requestAnimationFrame(h)}};let u=null;const m=(m,d)=>{i=!0,u=t.getBoundingClientRect(),n=m-u.left,s=d-u.top;const p=window.getComputedStyle(t).transform;if(p&&"none"!==p){const t=new DOMMatrix(p);a=t.m41,r=t.m42}else a=u.left,r=u.top;c=a,l=r,document.body.style.userSelect="none",e.classList.add("dragging"),o&&cancelAnimationFrame(o),h()},d=()=>{i=!1,o&&(cancelAnimationFrame(o),o=null),document.body.style.userSelect="",e.classList.remove("dragging")};e.addEventListener("mousedown",function(t){t.preventDefault(),m(t.clientX,t.clientY)}),e.addEventListener("touchstart",function(t){const e=t?.touches?.[0];e&&(m(e.clientX,e.clientY),t.preventDefault())},{passive:!1}),document.addEventListener("mousemove",function(t){i&&u&&(c=t.clientX-n,l=t.clientY-s)},{passive:!0}),document.addEventListener("touchmove",function(t){if(i&&u){const e=t?.touches?.[0];if(!e)return;c=e.clientX-n,l=e.clientY-s,t.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",d),document.addEventListener("touchend",d),document.addEventListener("touchcancel",d)}Z(t){(0,console.info)(`${this.name}: ${t}`),this.Y(this.N,"Status: "+t,!0)}J(t){(0,console.error)(`${this.name}: ${t}`),this.Y(this.N,"Error: "+t,!0)}}(c,l),d=new class{constructor(t,e,n){i(this,o),this.name=t,this.version=e,this.t=n,this.K="1.0.0",this.tt=null,this.et="!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",this.m=1e3,this.nt=3,this.it=null,this.st=null,this.ot="bm-n",this.rt="div#map canvas.maplibregl-canvas",this.ct=null,this.lt="",this.ht=[],this.ut=null}dt(){if(document.body.contains(this.it))return this.it;document.getElementById(this.ot)?.remove();const t=document.querySelector(this.rt),e=document.createElement("canvas");return e.id=this.ot,e.className="maplibregl-canvas",e.style.position="absolute",e.style.top="0",e.style.left="0",e.style.height=t?.clientHeight*(window.devicePixelRatio||1)+"px",e.style.width=t?.clientWidth*(window.devicePixelRatio||1)+"px",e.height=t?.clientHeight*(window.devicePixelRatio||1),e.width=t?.clientWidth*(window.devicePixelRatio||1),e.style.zIndex="8999",e.style.pointerEvents="none",t?.parentElement?.appendChild(e),this.it=e,window.addEventListener("move",this.bt),window.addEventListener("zoom",this.ft),window.addEventListener("resize",this.wt),this.it}async vt(){return{whoami:this.name.replace(" ",""),scriptVersion:this.version,schemaVersion:this.K,templates:{}}}async xt(t,e,n){this.ut||(this.ut=await this.vt()),this.t.Z(`Creating template at ${n.join(", ")}...`);const i=new a({displayName:e,l:0,h:r(this.tt||0,this.et),file:t,coords:n});i.u=await i.v(this.m),this.ut.templates[`${i.l} ${i.h}`]={name:i.displayName,enabled:!0,tiles:i.u},this.ht=[],this.ht.push(i);const s=(new Intl.NumberFormat).format(i.p);this.t.Z(`Template created at ${n.join(", ")}! Total pixels: ${s}`)}yt(){}async gt(){this.ut||(this.ut=await this.vt())}async Mt(t,e){const n=this.m*this.nt;e=e[0].toString().padStart(4,"0")+","+e[1].toString().padStart(4,"0");const i=this.ht;i.sort((t,e)=>t.l-e.l);const s=i.map(t=>{const n=Object.keys(t.u).filter(t=>t.startsWith(e));if(0===n.length)return null;const i=n.map(e=>t.u[e]);return i?.[0]}).filter(Boolean);if(s.length>0){const t=i.filter(t=>Object.keys(t.u).filter(t=>t.startsWith(e)).length>0).reduce((t,e)=>t+(e.p||0),0),n=(new Intl.NumberFormat).format(t);this.t.Z(`Displaying ${s.length} template${1==s.length?"":"s"}. Total pixels: ${n}`)}const o=await createImageBitmap(t),a=new OffscreenCanvas(n,n),r=a.getContext("2d");r.imageSmoothingEnabled=!1,r.beginPath(),r.rect(0,0,n,n),r.clip(),r.clearRect(0,0,n,n),r.drawImage(o,0,0,n,n);for(const t of s)r.drawImage(t,0,0);return await a.convertToBlob({type:"image/png"})}$t(){}}(c,l,m),p=new class{constructor(t){this.Ct=t,this.Dt=!1,this.It=[],this.kt=[]}Tt(t){window.addEventListener("message",async e=>{const n=e.data,i=n.jsonData;if(!n||"blue-marble"!==n.source)return;if(!n.endpoint)return;const s=n.endpoint?.split("?")[0].split("/").filter(t=>t&&isNaN(Number(t))).filter(t=>t&&!t.includes(".")).pop();switch(s){case"me":if(i.status&&"2"!=i.status?.toString()[0])return void t.J("You are not logged in!\nCould not fetch userdata.");const e=Math.ceil(Math.pow(Math.floor(i.level)*Math.pow(30,.65),1/.65)-i.pixelsPainted);i.id||i.id,this.Ct.tt=i.id,t.Y("bm-f",`Username: ${function(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}(i.name)}`),t.Y("bm-b",`Droplets: ${(new Intl.NumberFormat).format(i.droplets)}`),t.Y("bm-6",`Next level in ${(new Intl.NumberFormat).format(e)} pixel${1==e?"":"s"}`);break;case"pixel":const s=n.endpoint.split("?")[0].split("/").filter(t=>t&&!isNaN(Number(t))),r=new URLSearchParams(n.endpoint.split("?")[1]),c=[r.get("x"),r.get("y")];if(this.It.length&&(!s.length||!c.length))return void t.J("Coordinates are malformed!\nDid you try clicking the canvas first?");this.It=[...s,...c];const l=(o=s,a=c,[parseInt(o[0])%4*1e3+parseInt(a[0]),parseInt(o[1])%4*1e3+parseInt(a[1])]),h=document.querySelectorAll("span");for(const t of h)if(t.textContent.trim().includes(`${l[0]}, ${l[1]}`)){let e=document.querySelector("#bm-5");const n=`(Tl X: ${s[0]}, Tl Y: ${s[1]}, Px X: ${c[0]}, Px Y: ${c[1]})`;e?e.textContent=n:(e=document.createElement("span"),e.id="bm-5",e.textContent=n,e.style="margin-left: calc(var(--spacing)*3); font-size: small;",t.parentNode.parentNode.parentNode.insertAdjacentElement("afterend",e))}break;case"tiles":let u=n.endpoint.split("/");u=[parseInt(u[u.length-2]),parseInt(u[u.length-1].replace(".png",""))];const m=n.blobID,d=n.blobData,p=await this.Ct.Mt(d,u);window.postMessage({source:"blue-marble",blobID:m,blobData:p,blink:n.blink});break;case"robots":this.Dt="false"==i.userscript?.toString().toLowerCase()}var o,a})}}(d);m.S(p),function(){let t=!1;m.L({id:"bm-l",style:"top: 10px; right: 75px;"}).L({id:"bm-7"}).L({id:"bm-g"}).B().j({alt:"Blue Marble Icon - Click to minimize/maximize",src:"https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png",style:"cursor: pointer;"},(e,n)=>{n.addEventListener("click",()=>{t=!t;const i=document.querySelector("#bm-l"),s=document.querySelector("#bm-7"),o=document.querySelector("#bm-g"),a=document.querySelector("#bm-8"),r=document.querySelector("#bm-c"),c=document.querySelector("#bm-d"),l=document.querySelectorAll("#bm-8 input");t||(i.style.width="auto",i.style.maxWidth="300px",i.style.minWidth="200px",i.style.padding="10px"),["#bm-l h1","#bm-4","#bm-l hr","#bm-3 > *:not(#bm-8)","#bm-2","#bm-1",`#${e.N}`].forEach(e=>{document.querySelectorAll(e).forEach(e=>{e.style.display=t?"none":""})}),t?(a&&(a.style.display="none"),r&&(r.style.display="none"),c&&(c.style.display="none"),l.forEach(t=>{t.style.display="none"}),i.style.width="60px",i.style.height="76px",i.style.maxWidth="60px",i.style.minWidth="60px",i.style.padding="8px",n.style.marginLeft="3px",s.style.textAlign="center",s.style.margin="0",s.style.marginBottom="0",o&&(o.style.display="",o.style.marginBottom="0.25em")):(a&&(a.style.display="",a.style.flexDirection="",a.style.justifyContent="",a.style.alignItems="",a.style.gap="",a.style.textAlign="",a.style.margin=""),r&&(r.style.display=""),c&&(c.style.display="",c.style.marginTop=""),l.forEach(t=>{t.style.display=""}),n.style.marginLeft="",i.style.padding="10px",s.style.textAlign="",s.style.margin="",s.style.marginBottom="",o&&(o.style.marginBottom="0.5em"),i.style.width="",i.style.height=""),n.alt=t?"Blue Marble Icon - Minimized (Click to maximize)":"Blue Marble Icon - Maximized (Click to minimize)"})}).B().F(1,{textContent:c}).B().B().P().B().L({id:"bm-4"}).H({id:"bm-f",textContent:"Username:"}).B().H({id:"bm-b",textContent:"Droplets:"}).B().H({id:"bm-6",textContent:"Next level in..."}).B().B().P().B().L({id:"bm-3"}).L({id:"bm-8"}).A({id:"bm-c",className:"bm-p",style:"margin-top: 0;",innerHTML:''},(t,e)=>{e.onclick=()=>{const e=t.T?.It;e?.[0]?(t.Y("bm-h",e?.[0]||""),t.Y("bm-i",e?.[1]||""),t.Y("bm-j",e?.[2]||""),t.Y("bm-k",e?.[3]||"")):t.J("Coordinates are malformed! Did you try clicking on the canvas first?")}}).B().G({type:"number",id:"bm-h",placeholder:"Tl X",min:0,max:2047,step:1,required:!0}).B().G({type:"number",id:"bm-i",placeholder:"Tl Y",min:0,max:2047,step:1,required:!0}).B().G({type:"number",id:"bm-j",placeholder:"Px X",min:0,max:2047,step:1,required:!0}).B().G({type:"number",id:"bm-k",placeholder:"Px Y",min:0,max:2047,step:1,required:!0}).B().B().U({id:"bm-2",textContent:"Upload Template",accept:"image/png, image/jpeg, image/webp, image/bmp, image/gif"}).B().L({id:"bm-0"}).A({id:"bm-d",textContent:"Enable"},(t,e)=>{e.onclick=()=>{const e=document.querySelector("#bm-2"),n=document.querySelector("#bm-h");if(!n.checkValidity())return n.reportValidity(),void t.J("Coordinates are malformed! Did you try clicking on the canvas first?");const i=document.querySelector("#bm-i");if(!i.checkValidity())return i.reportValidity(),void t.J("Coordinates are malformed! Did you try clicking on the canvas first?");const s=document.querySelector("#bm-j");if(!s.checkValidity())return s.reportValidity(),void t.J("Coordinates are malformed! Did you try clicking on the canvas first?");const o=document.querySelector("#bm-k");if(!o.checkValidity())return o.reportValidity(),void t.J("Coordinates are malformed! Did you try clicking on the canvas first?");e?.files[0]?(d.xt(e.files[0],e.files[0]?.name.replace(/\.[^/.]+$/,""),[Number(n.value),Number(i.value),Number(s.value),Number(o.value)]),t.Z("Drew to canvas!")):t.J("No file selected!")}}).B().B().W({id:m.N,placeholder:`Status: Sleeping...\nVersion: ${l}`,readOnly:!0}).B().L({id:"bm-1"}).L().A({id:"bm-9",className:"bm-p",innerHTML:"🎨",title:"Template Color Converter"},(t,e)=>{e.addEventListener("click",()=>{window.open("https://pepoafonso.github.io/color_converter_wplace/","_blank","noopener noreferrer")})}).B().B().q({textContent:"Made by SwingTheVine",style:"margin-top: auto;"}).B().B().B().O(document.body)}(),m.X("#bm-l","#bm-g"),p.Tt(m),new MutationObserver((t,e)=>{const n=document.querySelector("#color-1");if(!n)return;let i=document.querySelector("#bm-e");i||(i=document.createElement("button"),i.id="bm-e",i.textContent="Move ↑",i.className="btn btn-soft",i.onclick=function(){const t=this.parentNode.parentNode.parentNode.parentNode,e="Move ↑"==this.textContent;t.parentNode.className=t.parentNode.className.replace(e?"bottom":"top",e?"top":"bottom"),t.style.borderTopLeftRadius=e?"0px":"var(--radius-box)",t.style.borderTopRightRadius=e?"0px":"var(--radius-box)",t.style.borderBottomLeftRadius=e?"var(--radius-box)":"0px",t.style.borderBottomRightRadius=e?"var(--radius-box)":"0px",this.textContent=e?"Move ↓":"Move ↑"},n.parentNode.parentNode.parentNode.parentNode.querySelector("h2").parentNode.appendChild(i))}).observe(document.body,{childList:!0,subtree:!0}),function(...t){(0,console.log)(...t)}(`%c${c}%c (${l}) userscript has loaded!`,"color: cornflowerblue;","")})(); diff --git a/docs/README.md b/docs/README.md index 7415767..5d7d1dd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -39,7 +39,7 @@

Blue Marble

-Latest Version +Latest Version Latest Release Software License: MPL-2.0 Contact Me @@ -47,7 +47,7 @@ Total Patches Total Lines of Code Total Comments -Compression +Compression Build CodeQL diff --git a/package-lock.json b/package-lock.json index d2482c4..e8c0061 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wplace-bluemarble", - "version": "0.70.4", + "version": "0.71.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wplace-bluemarble", - "version": "0.70.4", + "version": "0.71.0", "devDependencies": { "esbuild": "^0.25.0", "terser": "^5.43.1" diff --git a/package.json b/package.json index f8f574d..2c05718 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wplace-bluemarble", - "version": "0.71.0", + "version": "0.72.0", "type": "module", "scripts": { "build": "node build/build.js", diff --git a/src/BlueMarble.meta.js b/src/BlueMarble.meta.js index a6a2466..d91591a 100644 --- a/src/BlueMarble.meta.js +++ b/src/BlueMarble.meta.js @@ -1,20 +1,20 @@ // ==UserScript== // @name Blue Marble // @namespace https://github.com/SwingTheVine/ -// @version 0.71.0 +// @version 0.72.0 // @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. // @author SwingTheVine // @license MPL-2.0 // @supportURL https://discord.gg/tpeBPy46hf // @homepageURL https://github.com/SwingTheVine/Wplace-BlueMarble -// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/a3ff8840069a733d4cf4c27b8336beddf3706b60/dist/assets/Favicon.png +// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/6b6a45cba9943e5960c9887654aa704a90c02636/dist/assets/Favicon.png // @updateURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble.user.js // @downloadURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble.user.js // @run-at document-start // @match *://*.wplace.live/* // @grant GM_getResourceText // @grant GM_addStyle -// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/a3ff8840069a733d4cf4c27b8336beddf3706b60/dist/BlueMarble.user.css +// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/6b6a45cba9943e5960c9887654aa704a90c02636/dist/BlueMarble.user.css // ==/UserScript== // Wplace --> https://wplace.live diff --git a/src/Overlay.js b/src/Overlay.js index ea206fe..5d969bc 100644 --- a/src/Overlay.js +++ b/src/Overlay.js @@ -430,8 +430,9 @@ export default class Overlay { return this; } - /** Adds a file input to the overlay. This includes a container and a button. + /** Adds a file input to the overlay with enhanced visibility controls. * This input element will have properties shared between all file input elements in the overlay. + * Uses multiple hiding methods to prevent browser native text from appearing during minimize/maximize. * You can override the shared properties by using a callback. * @param {Object.} [additionalProperties={}] - The DOM properties of the file input that are NOT shared between all overlay file input elements. These should be camelCase. * @param {function(Overlay, HTMLDivElement, HTMLInputElement, HTMLButtonElement):void} [callback=()=>{}] - Additional JS modification to the file input. @@ -451,7 +452,10 @@ export default class Overlay { */ addInputFile(additionalProperties = {}, callback = () => {}) { - const properties = {'type': 'file', 'style': 'display: none;'}; // Shared file input DOM properties + const properties = { + 'type': 'file', + 'style': 'display: none !important; visibility: hidden !important; position: absolute !important; left: -9999px !important; width: 0 !important; height: 0 !important; opacity: 0 !important;' + }; // Complete file input hiding to prevent native browser text interference const text = additionalProperties['textContent'] ?? ''; // Retrieves the text content delete additionalProperties['textContent']; // Deletes the text content before applying the additional properties to the file input @@ -463,11 +467,15 @@ export default class Overlay { this.buildElement(); // Signifies that we are done adding children to the button this.buildElement(); // Signifies that we are done adding children to the container + // Prevent file input from being accessible or visible by screen-readers and tabbing + input.setAttribute('tabindex', '-1'); + input.setAttribute('aria-hidden', 'true'); + button.addEventListener('click', () => { input.click(); // Clicks the file input }); - // Changes the button text content (and trims the file name) + // Update button text when file is selected input.addEventListener('change', () => { button.style.maxWidth = `${button.offsetWidth}px`; if (input.files.length > 0) { @@ -534,13 +542,19 @@ export default class Overlay { } /** Handles dragging of the overlay. + * Uses requestAnimationFrame for smooth animations and GPU-accelerated transforms. * @param {string} moveMe - The ID of the element to be moved - * @param {string} iMoveThings - The ID of the element to be moved + * @param {string} iMoveThings - The ID of the drag handle element * @since 0.8.2 */ handleDrag(moveMe, iMoveThings) { let isDragging = false; let offsetX, offsetY = 0; + let animationFrame = null; + let currentX = 0; + let currentY = 0; + let targetX = 0; + let targetY = 0; // Retrieves the elements (allows either '#id' or 'id' to be passed in) moveMe = document.querySelector(moveMe?.[0] == '#' ? moveMe : '#' + moveMe); @@ -552,67 +566,110 @@ export default class Overlay { return; // Kills itself } - // What to do when the mouse is pressed down on the element that moves things - iMoveThings.addEventListener('mousedown', function(event) { + // Smooth animation loop using requestAnimationFrame for optimal performance + const updatePosition = () => { + if (isDragging) { + // Only update DOM if position changed significantly (reduce repaints) + const deltaX = Math.abs(currentX - targetX); + const deltaY = Math.abs(currentY - targetY); + + if (deltaX > 0.5 || deltaY > 0.5) { + currentX = targetX; + currentY = targetY; + + // Use CSS transform for GPU acceleration instead of left/top + moveMe.style.transform = `translate(${currentX}px, ${currentY}px)`; + moveMe.style.left = '0px'; + moveMe.style.top = '0px'; + moveMe.style.right = ''; + } + + animationFrame = requestAnimationFrame(updatePosition); + } + }; + + // Cache initial position to avoid expensive getBoundingClientRect calls during drag + let initialRect = null; + + const startDrag = (clientX, clientY) => { isDragging = true; - offsetX = event.clientX - moveMe.getBoundingClientRect().left; - offsetY = event.clientY - moveMe.getBoundingClientRect().top; - document.body.style.userSelect = 'none'; // Prevents text selection while dragging - iMoveThings.classList.add('dragging'); // Adds a class to indicate a dragging state + initialRect = moveMe.getBoundingClientRect(); + offsetX = clientX - initialRect.left; + offsetY = clientY - initialRect.top; + + // Get current position from transform or use element position + const computedStyle = window.getComputedStyle(moveMe); + const transform = computedStyle.transform; + + if (transform && transform !== 'none') { + const matrix = new DOMMatrix(transform); + currentX = matrix.m41; + currentY = matrix.m42; + } else { + currentX = initialRect.left; + currentY = initialRect.top; + } + + targetX = currentX; + targetY = currentY; + + document.body.style.userSelect = 'none'; + iMoveThings.classList.add('dragging'); + + // Start animation loop + if (animationFrame) { + cancelAnimationFrame(animationFrame); + } + updatePosition(); + }; + + const endDrag = () => { + isDragging = false; + if (animationFrame) { + cancelAnimationFrame(animationFrame); + animationFrame = null; + } + document.body.style.userSelect = ''; + iMoveThings.classList.remove('dragging'); + }; + + // Mouse down - start dragging + iMoveThings.addEventListener('mousedown', function(event) { + event.preventDefault(); + startDrag(event.clientX, event.clientY); }); - // What to do when the touch starts on the element that moves things + // Touch start - start dragging iMoveThings.addEventListener('touchstart', function(event) { - isDragging = true; const touch = event?.touches?.[0]; if (!touch) {return;} - offsetX = touch.clientX - moveMe.getBoundingClientRect().left; // Distance between the left edge of the overlay, and the cursor - offsetY = touch.clientY - moveMe.getBoundingClientRect().top; // Distance between the top edge of the overlay, and the cursor - document.body.style.userSelect = 'none'; // Prevents text selection while dragging - iMoveThings.classList.add('dragging'); // Adds a class to indicate a dragging state - }, { passive: false }); // Prevents scrolling from being captured + startDrag(touch.clientX, touch.clientY); + event.preventDefault(); + }, { passive: false }); - // What to do when the mouse is moved while dragging + // Mouse move - update target position document.addEventListener('mousemove', function(event) { - if (isDragging) { - moveMe.style.left = (event.clientX - offsetX) + 'px'; // Binds the overlay to the left side of the screen, and sets it's position to the cursor - moveMe.style.top = (event.clientY - offsetY) + 'px'; // Binds the overlay to the top of the screen, and sets it's position to the cursor - moveMe.style.right = ''; // Destroys the right property to unbind the overlay from the right side of the screen + if (isDragging && initialRect) { + targetX = event.clientX - offsetX; + targetY = event.clientY - offsetY; } - }); + }, { passive: true }); - // What to do when the touch moves while dragging + // Touch move - update target position document.addEventListener('touchmove', function(event) { - if (isDragging) { + if (isDragging && initialRect) { const touch = event?.touches?.[0]; if (!touch) {return;} - moveMe.style.left = (touch.clientX - offsetX) + 'px'; // Binds the overlay to the left side of the screen, and sets it's position to the cursor - moveMe.style.top = (touch.clientY - offsetY) + 'px'; // Binds the overlay to the top of the screen, and sets it's position to the cursor - moveMe.style.right = ''; // Destroys the right property to unbind the overlay from the right side of the screen - event.preventDefault(); // prevent scrolling while dragging + targetX = touch.clientX - offsetX; + targetY = touch.clientY - offsetY; + event.preventDefault(); } - }, { passive: false }); // Prevents scrolling from being captured + }, { passive: false }); - // What to do when the mouse is released - document.addEventListener('mouseup', function() { - isDragging = false; - document.body.style.userSelect = ''; // Restores text selection capability after dragging - iMoveThings.classList.remove('dragging'); // Removes the dragging class - }); - - // What to do when the touch ends - document.addEventListener('touchend', function() { - isDragging = false; - document.body.style.userSelect = ''; // Restores text selection capability after dragging - iMoveThings.classList.remove('dragging'); // Removes the dragging class - }); - - // What to do when the touch is cancelled - document.addEventListener('touchcancel', function() { - isDragging = false; - document.body.style.userSelect = ''; // Restores text selection capability after dragging - iMoveThings.classList.remove('dragging'); // Removes the dragging class - }); + // End drag events + document.addEventListener('mouseup', endDrag); + document.addEventListener('touchend', endDrag); + document.addEventListener('touchcancel', endDrag); } /** Handles status display. diff --git a/src/main.js b/src/main.js index c8dacba..4fb878d 100644 --- a/src/main.js +++ b/src/main.js @@ -1,19 +1,5 @@ /** The main file. Everything in the userscript is executed from here. * @since 0.0.0 - * - * VERSION HISTORY: - * 0.71.0 - Added minimize/maximize functionality and pixel counting system - * Features added: - * - Interactive minimize/maximize overlay with click-to-toggle functionality - * - Fixed overlay dimensions: 60px width × 76px height in minimized state - * - Smart element visibility control (hides all UI except icon and drag bar when minimized) - * - Icon repositioning system (3px right offset) for better visual alignment in minimized state - * - Comprehensive pixel counting system for template statistics - * - Real-time pixel count display in template creation and rendering status messages - * - Intelligent pixel counting for actively rendered templates with tile-based filtering - * - Internationalized number formatting for large pixel counts (e.g., "1,234,567 pixels") - * - Automatic state management with proper cleanup when switching between modes - * - Enhanced user experience with visual feedback and status updates */ import Overlay from './Overlay.js'; @@ -247,19 +233,6 @@ function observeBlack() { * Creates a responsive overlay UI that can toggle between full-featured and minimized states. * * Parent/child relationships in the DOM structure below are indicated by indentation. - * - * OVERLAY STATES: - * - MAXIMIZED: Full UI with all controls, inputs, and status information visible - * - MINIMIZED: Compact 60×76px interface showing only the Blue Marble icon and drag functionality - * - * FEATURES: - * - Click-to-toggle functionality on the Blue Marble icon - * - Automatic element visibility management - * - Fixed dimensions for consistent minimized appearance - * - Proper cleanup and restoration of all UI elements - * - Visual feedback through alt-text updates - * - Status message integration - * * @since 0.58.3 */ function buildOverlayMain() { @@ -285,13 +258,6 @@ function buildOverlayMain() { * - Resets icon positioning to default alignment * - Shows success message when returning to maximized state * - * IMPLEMENTATION DETAILS: - * - Uses CSS display property manipulation for element visibility - * - Maintains drag functionality in both states - * - Updates accessibility text (alt attribute) based on current state - * - Provides user feedback through status messages - * - Ensures proper cleanup of all style overrides when switching states - * * @param {Event} event - The click event object (implicit) */ img.addEventListener('click', () => { diff --git a/src/overlay.css b/src/overlay.css index c239ac3..7a0a12e 100644 --- a/src/overlay.css +++ b/src/overlay.css @@ -11,12 +11,18 @@ transition: all 0.3s ease; max-width: 300px; width: auto; + /* Performance optimizations for smooth dragging */ + will-change: transform; + backface-visibility: hidden; + -webkit-backface-visibility: hidden; + transform-style: preserve-3d; + -webkit-transform-style: preserve-3d; } /* Smooth transitions for minimize/maximize functionality */ #bm-contain-userinfo, #bm-overlay hr, -#bm-contain-automation, +#bm-contain-automation, #bm-contain-buttons-action { transition: opacity 0.2s ease, height 0.2s ease; } @@ -49,6 +55,20 @@ div#bm-overlay { cursor: grabbing; } +/* Disable interactions during drag for better performance */ +#bm-overlay:has(#bm-bar-drag.dragging) { + pointer-events: none; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +/* Keep drag bar interactive when dragging */ +#bm-bar-drag.dragging { + pointer-events: auto; +} + /* The container for the overlay header */ #bm-contain-header { margin-bottom: 0.5em; @@ -188,6 +208,21 @@ div:has(> #bm-input-file-template) > button { text-overflow: ellipsis; } +/* Force complete invisibility of file input to prevent native browser text */ +#bm-input-file-template, +input[type="file"][id*="template"] { + display: none !important; + visibility: hidden !important; + position: absolute !important; + left: -9999px !important; + top: -9999px !important; + width: 0 !important; + height: 0 !important; + opacity: 0 !important; + z-index: -9999 !important; + pointer-events: none !important; +} + /* Output status area */ #bm-output-status { font-size: small;