diff --git a/dist/BlueMarble-For-GreasyFork.user.css b/dist/BlueMarble-For-GreasyFork.user.css index eb9d159..51f4238 100644 --- a/dist/BlueMarble-For-GreasyFork.user.css +++ b/dist/BlueMarble-For-GreasyFork.user.css @@ -1,131 +1,3 @@ -/* src/WindowFilter.css */ -#bm-window-filter p svg { - display: inline; - height: 1em; - fill: white; -} -#bm-filter-flex { - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: center; - gap: 1em 3ch; -} -.bm-filter-color { - width: fit-content; - max-width: 35ch; - background-color: rgba(21, 48, 99, 0.9); - border-radius: 1em; - padding: 0.5em; - gap: 1ch; - transition: background-color 0.3s ease; -} -.bm-filter-color:hover, -.bm-filter-color:focus-within { - background-color: rgba(17, 40, 85, 0.9); -} -.bm-filter-container-rgb { - display: block; - border: thick double darkslategray; - width: fit-content; - height: fit-content; - padding: 1ch; -} -.bm-filter-color[data-id="-2"] .bm-filter-container-rgb { - background: - conic-gradient( - #aa0000 0%, - #aaaa00 16.6%, - #00aa00 33.3%, - #00aaaa 50%, - #0000aa 66.6%, - #aa00aa 83.3%, - #aa0000 100%); -} -.bm-filter-color[data-id="-1"] .bm-filter-container-rgb { - background: url('data:image/svg+xml;utf8,') repeat; - background-color: transparent !important; -} -.bm-filter-color[data-id="-1"] .bm-filter-container-rgb svg { - fill: white !important; -} -.bm-filter-color[data-id="0"] .bm-filter-container-rgb { - background-color: transparent !important; -} -#bm-window-filter .bm-filter-container-rgb button { - padding: 0.75em 0.5ch; -} -.bm-filter-container-rgb svg { - width: 4ch; -} -.bm-filter-color > .bm-flex-between { - flex-direction: column; - align-items: flex-start; - gap: 0; -} -.bm-filter-color small { - font-size: 0.75em; -} -#bm-window-filter .bm-filter-color.bm-color-hide { - display: none; -} -.bm-windowed #bm-filter-flex { - flex-direction: column; - gap: 0.25em; -} -.bm-windowed .bm-filter-color { - width: auto; - margin: 0; - padding: 0; -} -.bm-windowed .bm-filter-container-rgb { - display: flex; - width: 100%; - gap: 0.5ch; - align-items: center; - padding: 0.1em 0.5ch; - border: none; - border-radius: 1em; -} -#bm-window-filter.bm-windowed .bm-filter-container-rgb button { - padding: 0.5em 0.25ch; -} -.bm-windowed .bm-filter-container-rgb svg { - width: 3ch; -} -.bm-windowed .bm-filter-color h2 { - font-size: 0.75em; -} - -/* src/WindowWizard.css */ -#bm-wizard-tlist { - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: flex-start; -} -#bm-wizard-tlist > .bm-container { - width: 100%; - justify-content: flex-start; - background-color: rgba(21, 48, 99, 0.9); - border-radius: 1em; - padding: 0.5em; - transition: background-color 0.3s ease; -} -#bm-wizard-tlist > .bm-container:hover, -#bm-wizard-tlist > .bm-container:focus-within { - background-color: rgba(17, 40, 85, 0.9); -} -#bm-wizard-tlist .bm-wizard-template-container-image { - height: 100%; - font-size: xxx-large; -} -#bm-wizard-tlist .bm-wizard-template-container-flavor { - flex-direction: column; - align-items: flex-start; - gap: 0; -} - /* src/confettiManager.css */ div:has(> confetti-piece) { position: absolute; @@ -222,7 +94,8 @@ confetti-piece { font-weight: bold; vertical-align: middle; } -.bm-dragbar h1 { +.bm-dragbar h1, +.bm-dragbar-text { font-size: 1.2em; user-select: none; overflow: hidden; @@ -406,11 +279,201 @@ input[type=file] { font-size: 1.6em; font-family: monospace; } -.bm-container:not(#bm-window-main .bm-container) { - margin: 0.25em 0; +.bm-windowed .bm-container:not(#bm-window-main .bm-container) { + margin-top: 0.25em; + margin-bottom: 0.25em; } .bm-windowed h1:not(#bm-window-main h1) { font-size: 1em; } +/* src/WindowFilter.css */ +#bm-window-filter p svg { + display: inline; + height: 1em; + fill: white; +} +#bm-filter-flex { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + gap: 1em 3ch; +} +#bm-window-filter .bm-filter-color { + width: fit-content; + max-width: 35ch; + background-color: rgba(21, 48, 99, 0.9); + border-radius: 1em; + padding: 0.5em; + gap: 1ch; + transition: background-color 0.3s ease; +} +#bm-window-filter .bm-filter-color:hover, +#bm-window-filter.bm-filter-color:focus-within { + background-color: rgba(17, 40, 85, 0.9); +} +#bm-window-filter .bm-filter-container-rgb { + display: block; + border: thick double darkslategray; + width: fit-content; + height: fit-content; + padding: 1ch; +} +#bm-window-filter .bm-filter-color[data-id="-2"] .bm-filter-container-rgb { + background: + conic-gradient( + #aa0000 0%, + #aaaa00 16.6%, + #00aa00 33.3%, + #00aaaa 50%, + #0000aa 66.6%, + #aa00aa 83.3%, + #aa0000 100%); +} +#bm-window-filter .bm-filter-color[data-id="-1"] .bm-filter-container-rgb { + background: url('data:image/svg+xml;utf8,') repeat; + background-color: transparent !important; +} +#bm-window-filter .bm-filter-color[data-id="-1"] .bm-filter-container-rgb svg { + fill: white !important; +} +#bm-window-filter .bm-filter-color[data-id="0"] .bm-filter-container-rgb { + background-color: transparent !important; +} +#bm-window-filter .bm-filter-container-rgb button { + padding: 0.75em 0.5ch; +} +#bm-window-filter .bm-filter-container-rgb svg { + width: 4ch; +} +#bm-window-filter .bm-filter-color > .bm-flex-between { + flex-direction: column; + align-items: flex-start; + gap: 0; +} +#bm-window-filter .bm-filter-color small { + font-size: 0.75em; +} +#bm-window-filter .bm-filter-color.bm-color-hide { + display: none; +} +#bm-window-filter.bm-windowed #bm-filter-flex { + flex-direction: column; + gap: 0.25em; +} +#bm-window-filter.bm-windowed .bm-filter-color { + width: auto; + margin: 0; + padding: 0; +} +#bm-window-filter.bm-windowed .bm-filter-container-rgb { + display: flex; + width: 100%; + gap: 0.5ch; + align-items: center; + padding: 0.1em 0.5ch; + border: none; + border-radius: 1em; +} +#bm-window-filter.bm-windowed .bm-filter-container-rgb button { + padding: 0.5em 0.25ch; +} +#bm-window-filter.bm-windowed .bm-filter-container-rgb svg { + width: 3ch; +} +#bm-window-filter.bm-windowed .bm-filter-color h2 { + font-size: 0.75em; +} +#bm-window-filter #bm-filter-windowed-color-totals { + font-size: 1em; +} + +/* src/WindowSettings.css */ +#bm-window-settings div:has(> .bm-highlight-preset-container) { + width: fit-content; + justify-content: flex-start; +} +#bm-window-settings .bm-highlight-preset-container { + display: flex; + flex-direction: column; + width: 13%; +} +#bm-window-settings .bm-highlight-preset-container span { + width: fit-content; + margin: auto; + font-size: 0.7em; +} +#bm-window-settings .bm-highlight-preset-container button { + width: fit-content; + padding: 0; + border-radius: 0; +} +#bm-window-settings .bm-highlight-preset-container svg { + stroke: #333; + stroke-width: 0.02px; + width: 100%; + min-width: 1.5ch; + max-width: 14.5ch; +} +#bm-window-settings .bm-highlight-preset-container button:hover svg, +#bm-window-settings .bm-highlight-preset-container button:focus svg { + opacity: 0.9; +} +#bm-window-settings .bm-highlight-grid { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + width: 25%; + min-width: 3ch; + max-width: 15ch; +} +#bm-window-settings .bm-highlight-grid > button { + width: 100%; + padding: 0; + aspect-ratio: 1 / 1; + background-color: white; + border: #333 1px solid; + border-radius: 0; + box-sizing: border-box; +} +#bm-window-settings .bm-highlight-grid > button[data-status=Incorrect] { + background-color: brown; +} +#bm-window-settings .bm-highlight-grid > button[data-status=Template] { + background-color: darkslategray; +} +#bm-window-settings .bm-highlight-grid > button:hover, +#bm-window-settings .bm-highlight-grid > button:focus { + opacity: 0.8; +} + +/* src/WindowWizard.css */ +#bm-wizard-tlist { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; +} +#bm-wizard-tlist > .bm-container { + width: 100%; + justify-content: flex-start; + background-color: rgba(21, 48, 99, 0.9); + border-radius: 1em; + padding: 0.5em; + transition: background-color 0.3s ease; +} +#bm-wizard-tlist > .bm-container:hover, +#bm-wizard-tlist > .bm-container:focus-within { + background-color: rgba(17, 40, 85, 0.9); +} +#bm-wizard-tlist .bm-wizard-template-container-image { + height: 100%; + font-size: xxx-large; +} +#bm-wizard-tlist .bm-wizard-template-container-flavor { + flex-direction: column; + align-items: flex-start; + gap: 0; +} + /* src/main.css */ diff --git a/dist/BlueMarble-For-GreasyFork.user.js b/dist/BlueMarble-For-GreasyFork.user.js index 10597ba..f86342c 100644 --- a/dist/BlueMarble-For-GreasyFork.user.js +++ b/dist/BlueMarble-For-GreasyFork.user.js @@ -2,14 +2,14 @@ // @name Blue Marble // @name:en Blue Marble // @namespace https://github.com/SwingTheVine/ -// @version 0.91.0 +// @version 0.91.102 // @description A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features. // @description:en A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features. // @author SwingTheVine // @license MPL-2.0 // @supportURL https://discord.gg/tpeBPy46hf // @homepageURL https://bluemarble.lol/ -// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/ffa17bc9a7c2db10efc201437dbf1637e11a6f61/dist/assets/Favicon.png +// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/78477321232b29c09e3794c360068d7d23a0172c/dist/assets/Favicon.png // @updateURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble-For-GreasyFork.user.js // @downloadURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble-For-GreasyFork.user.js // @match https://wplace.live/* @@ -21,7 +21,7 @@ // @grant GM_xmlhttpRequest // @grant GM.download // @connect telemetry.thebluecorner.net -// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/ffa17bc9a7c2db10efc201437dbf1637e11a6f61/dist/BlueMarble-For-GreasyFork.user.css +// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/78477321232b29c09e3794c360068d7d23a0172c/dist/BlueMarble-For-GreasyFork.user.css // @antifeature tracking Anonymous opt-in telemetry data // @noframes // ==/UserScript== @@ -317,196 +317,6 @@ { "id": 63, "premium": true, "name": "Light Stone", "rgb": [205, 197, 158] } ]; - // src/Template.js - var _Template_instances, calculateTotalPixelsFromImageData_fn; - var Template = class { - /** The constructor for the {@link Template} class with enhanced pixel tracking. - * @param {Object} [params={}] - Object containing all optional parameters - * @param {string} [params.displayName='My template'] - The display name of the template - * @param {number} [params.sortID=0] - The sort number of the template for rendering priority - * @param {string} [params.authorID=''] - The user ID of the person who exported the template (prevents sort ID collisions) - * @param {string} [params.url=''] - The URL to the source image - * @param {File} [params.file=null] - The template file (pre-processed File or processed bitmap) - * @param {Array} [params.coords=null] - The coordinates of the top left corner as (tileX, tileY, pixelX, pixelY) - * @param {Object} [params.chunked=null] - The affected chunks of the template, and their template for each chunk as a bitmap - * @param {Object} [params.chunked32={}] - The affected chunks of the template, and their template for each chunk as a Uint32Array - * @param {number} [params.tileSize=1000] - The size of a tile in pixels (assumes square tiles) - * @param {Object} [params.pixelCount={total:0, colors:Map}] - Total number of pixels in the template (calculated automatically during processing) - * @since 0.65.2 - */ - constructor({ - displayName = "My template", - sortID = 0, - authorID = "", - url = "", - file = null, - coords: coords2 = null, - chunked = null, - chunked32 = {}, - tileSize = 1e3 - } = {}) { - __privateAdd(this, _Template_instances); - this.displayName = displayName; - this.sortID = sortID; - this.authorID = authorID; - this.url = url; - this.file = file; - this.coords = coords2; - this.chunked = chunked; - this.chunked32 = chunked32; - this.tileSize = tileSize; - this.pixelCount = { total: 0, colors: /* @__PURE__ */ new Map() }; - } - /** Creates chunks of the template for each tile. - * @param {Number} tileSize - Size of the tile as determined by templateManager - * @param {Object} paletteBM - An collection of Uint32Arrays containing the palette BM uses - * @returns {Object} Collection of template bitmaps & buffers organized by tile coordinates - * @since 0.65.4 - */ - async createTemplateTiles(tileSize, paletteBM) { - console.log("Template coordinates:", this.coords); - const shreadSize = 3; - const bitmap = await createImageBitmap(this.file); - const imageWidth = bitmap.width; - const imageHeight = bitmap.height; - this.tileSize = tileSize; - const templateTiles = {}; - const templateTilesBuffers = {}; - const canvas = new OffscreenCanvas(this.tileSize, this.tileSize); - const context = canvas.getContext("2d", { willReadFrequently: true }); - canvas.width = imageWidth; - canvas.height = imageHeight; - context.imageSmoothingEnabled = false; - context.drawImage(bitmap, 0, 0); - let timer = Date.now(); - const totalPixelMap = __privateMethod(this, _Template_instances, calculateTotalPixelsFromImageData_fn).call(this, context.getImageData(0, 0, imageWidth, imageHeight), paletteBM); - console.log(`Calculating total pixels took ${(Date.now() - timer) / 1e3} seconds`); - let totalPixels = 0; - const transparentColorID = 0; - for (const [color, total] of totalPixelMap) { - if (color == transparentColorID) { - continue; - } - totalPixels += total; - } - this.pixelCount = { total: totalPixels, colors: totalPixelMap }; - timer = Date.now(); - const canvasMask = new OffscreenCanvas(3, 3); - const contextMask = canvasMask.getContext("2d"); - contextMask.clearRect(0, 0, 3, 3); - contextMask.fillStyle = "white"; - contextMask.fillRect(1, 1, 1, 1); - for (let pixelY = this.coords[3]; pixelY < imageHeight + this.coords[3]; ) { - const drawSizeY = Math.min(this.tileSize - pixelY % this.tileSize, imageHeight - (pixelY - this.coords[3])); - console.log(`Math.min(${this.tileSize} - (${pixelY} % ${this.tileSize}), ${imageHeight} - (${pixelY - this.coords[3]}))`); - for (let pixelX = this.coords[2]; pixelX < imageWidth + this.coords[2]; ) { - console.log(`Pixel X: ${pixelX} -Pixel Y: ${pixelY}`); - const drawSizeX = Math.min(this.tileSize - pixelX % this.tileSize, imageWidth - (pixelX - this.coords[2])); - console.log(`Math.min(${this.tileSize} - (${pixelX} % ${this.tileSize}), ${imageWidth} - (${pixelX - this.coords[2]}))`); - console.log(`Draw Size X: ${drawSizeX} -Draw Size Y: ${drawSizeY}`); - const canvasWidth = drawSizeX * shreadSize; - const canvasHeight = drawSizeY * shreadSize; - canvas.width = canvasWidth; - canvas.height = canvasHeight; - console.log(`Draw X: ${drawSizeX} -Draw Y: ${drawSizeY} -Canvas Width: ${canvasWidth} -Canvas Height: ${canvasHeight}`); - context.imageSmoothingEnabled = false; - console.log(`Getting X ${pixelX}-${pixelX + drawSizeX} -Getting Y ${pixelY}-${pixelY + drawSizeY}`); - context.clearRect(0, 0, canvasWidth, canvasHeight); - context.drawImage( - bitmap, - // Bitmap image to draw - pixelX - this.coords[2], - // Coordinate X to draw *from* - pixelY - this.coords[3], - // Coordinate Y to draw *from* - drawSizeX, - // X width to draw *from* - drawSizeY, - // Y height to draw *from* - 0, - // Coordinate X to draw *at* - 0, - // Coordinate Y to draw *at* - drawSizeX * shreadSize, - // X width to draw *at* - drawSizeY * shreadSize - // Y height to draw *at* - ); - context.save(); - context.globalCompositeOperation = "destination-in"; - context.fillStyle = context.createPattern(canvasMask, "repeat"); - context.fillRect(0, 0, canvasWidth, canvasHeight); - context.restore(); - const imageData = context.getImageData(0, 0, canvasWidth, canvasHeight); - console.log(`Shreaded pixels for ${pixelX}, ${pixelY}`, imageData); - const templateTileName = `${(this.coords[0] + Math.floor(pixelX / 1e3)).toString().padStart(4, "0")},${(this.coords[1] + Math.floor(pixelY / 1e3)).toString().padStart(4, "0")},${(pixelX % 1e3).toString().padStart(3, "0")},${(pixelY % 1e3).toString().padStart(3, "0")}`; - this.chunked32[templateTileName] = new Uint32Array(imageData.data.buffer); - templateTiles[templateTileName] = await createImageBitmap(canvas); - const canvasBlob = await canvas.convertToBlob(); - const canvasBuffer = await canvasBlob.arrayBuffer(); - const canvasBufferBytes = Array.from(new Uint8Array(canvasBuffer)); - templateTilesBuffers[templateTileName] = uint8ToBase64(canvasBufferBytes); - console.log(templateTiles); - pixelX += drawSizeX; - } - pixelY += drawSizeY; - } - console.log(`Parsing template took ${(Date.now() - timer) / 1e3} seconds`); - console.log("Template Tiles: ", templateTiles); - console.log("Template Tiles Buffers: ", templateTilesBuffers); - console.log("Template Tiles Uint32Array: ", this.chunked32); - return { templateTiles, templateTilesBuffers }; - } - /** Calculates top left coordinate of template. - * It uses `Template.chunked` to update `Template.coords` - * @since 0.88.504 - */ - calculateCoordsFromChunked() { - let topLeftCoord = [Infinity, Infinity, Infinity, Infinity]; - const tileKeys = Object.keys(this.chunked).sort(); - tileKeys.forEach((key, index) => { - const [tileX, tileY, pixelX, pixelY] = key.split(",").map(Number); - if (tileY < topLeftCoord[1] || tileY == topLeftCoord[1] && tileX < topLeftCoord[0]) { - topLeftCoord = [tileX, tileY, pixelX, pixelY]; - } - }); - this.coords = topLeftCoord; - } - }; - _Template_instances = new WeakSet(); - /** Calculates the total pixels for each color for the image. - * - * @param {ImageData} imageData - The pre-shreaded image "casted" onto a canvas - * @param {Object} paletteBM - The palette Blue Marble uses for colors - * @param {Number} paletteTolerance - How close an RGB color has to be in order to be considered a palette color. A tolerance of "3" means the sum of the RGB can be up to 3 away from the actual value. - * @returns {Map} A map where the key is the color ID, and the value is the total pixels for that color ID - * @since 0.88.6 - */ - calculateTotalPixelsFromImageData_fn = function(imageData, paletteBM) { - const buffer32Arr = new Uint32Array(imageData.data.buffer); - const { palette: _, LUT: lookupTable } = paletteBM; - const _colorpalette = /* @__PURE__ */ new Map(); - for (let pixelIndex = 0; pixelIndex < buffer32Arr.length; pixelIndex++) { - const pixel = buffer32Arr[pixelIndex]; - let bestColorID = -2; - if (pixel >>> 24 == 0) { - bestColorID = 0; - } else { - bestColorID = lookupTable.get(pixel) ?? -2; - } - const colorIDcount = _colorpalette.get(bestColorID); - _colorpalette.set(bestColorID, colorIDcount ? colorIDcount + 1 : 1); - } - console.log(_colorpalette); - return _colorpalette; - }; - // src/Overlay.js var _Overlay_instances, createElement_fn, applyAttribute_fn; var Overlay = class { @@ -521,6 +331,7 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`); this.name = name2; this.version = version2; this.apiManager = null; + this.settingsManager = null; this.outputStatusId = "bm-output-status"; this.overlay = null; this.currentParent = null; @@ -533,6 +344,13 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`); setApiManager(apiManager2) { this.apiManager = apiManager2; } + /** Populates the settingsManager variable with the settingsManager class. + * @param {SettingsManager} settingsManager - The settingsManager class instance + * @since 0.91.11 + */ + setSettingsManager(settingsManager2) { + this.settingsManager = settingsManager2; + } /** Finishes building an element. * Call this after you are finished adding children. * If the element will have no children, call it anyways. @@ -895,8 +713,15 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`); addCheckbox(additionalProperties = {}, callback = () => { }) { const properties = { "type": "checkbox" }; - const label = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "label", { "textContent": additionalProperties["textContent"] ?? "" }); - delete additionalProperties["textContent"]; + const labelContent = {}; + if (!!additionalProperties["textContent"]) { + labelContent["textContent"] = additionalProperties["textContent"]; + delete additionalProperties["textContent"]; + } else if (!!additionalProperties["innerHTML"]) { + labelContent["innerHTML"] = additionalProperties["innerHTML"]; + delete additionalProperties["textContent"]; + } + const label = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "label", labelContent); const checkbox = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "input", properties, additionalProperties); label.insertBefore(checkbox, label.firstChild); this.buildElement(); @@ -1713,15 +1538,1121 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`); (part, i) => i == 0 ? part : part[0].toUpperCase() + part.slice(1) ).join("")] = value; } else if (property.startsWith("aria")) { - const camelCase = property.slice(5).split("-").map( - (part, i) => i == 0 ? part : part[0].toUpperCase() + part.slice(1) - ).join(""); - element["aria" + camelCase[0].toUpperCase() + camelCase.slice(1)] = value; + element.setAttribute(property, value); } else { element[property] = value; } }; + // src/WindowSettings.js + var _WindowSettings_instances, errorOverrideFailure_fn; + var WindowSettings = class extends Overlay { + /** Constructor for the Settings window + * @param {string} name - The name of the userscript + * @param {string} version - The version of the userscript + * @since 0.91.11 + * @see {@link Overlay#constructor} for examples + */ + constructor(name2, version2) { + super(name2, version2); + __privateAdd(this, _WindowSettings_instances); + this.window = null; + this.windowID = "bm-window-settings"; + this.windowParent = document.body; + } + /** Spawns a Settings window. + * If another settings window already exists, we DON'T spawn another! + * Parent/child relationships in the DOM structure below are indicated by indentation. + * @since 0.91.11 + */ + buildWindow() { + if (document.querySelector(`#${this.windowID}`)) { + document.querySelector(`#${this.windowID}`).remove(); + return; + } + this.window = this.addDiv({ "id": this.windowID, "class": "bm-window" }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Color Filter"', "data-button-status": "expanded" }, (instance, button) => { + button.onclick = () => instance.handleMinimization(button); + button.ontouchend = () => { + button.click(); + }; + }).buildElement().addDiv().buildElement().addDiv({ "class": "bm-flex-center" }).addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Color Filter"' }, (instance, button) => { + button.onclick = () => { + document.querySelector(`#${this.windowID}`)?.remove(); + }; + button.ontouchend = () => { + button.click(); + }; + }).buildElement().buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container bm-center-vertically" }).addHeader(1, { "textContent": "Settings" }).buildElement().buildElement().addHr().buildElement().addP({ "textContent": "Settings take 5 seconds to save." }).buildElement().addDiv({ "class": "bm-container bm-scrollable" }, (instance, div) => { + this.buildHighlight(); + this.buildTemplate(); + }).buildElement().buildElement().buildElement().buildOverlay(this.windowParent); + this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`); + } + /** Builds the highlight section of the window. + * This should be overriden by {@link SettingsManager} + * @since 0.91.11 + */ + buildHighlight() { + __privateMethod(this, _WindowSettings_instances, errorOverrideFailure_fn).call(this, "Pixel Highlight"); + } + /** Builds the template section of the window. + * This should be overriden by {@link SettingsManager} + * @since 0.91.68 + */ + buildTemplate() { + __privateMethod(this, _WindowSettings_instances, errorOverrideFailure_fn).call(this, "Template"); + } + }; + _WindowSettings_instances = new WeakSet(); + /** Displays an error when a settings category fails to load. + * @param {string} name - The name of the category + * @since 0.91.11 + */ + errorOverrideFailure_fn = function(name2) { + this.window = this.addDiv({ "class": "bm-container" }).addHeader(2, { "textContent": name2 }).buildElement().addHr().buildElement().addP({ "innerHTML": `An error occured loading the ${name2} category. SettingsManager failed to override the ${name2} function inside WindowSettings.` }).buildElement().buildElement(); + }; + + // src/settingsManager.js + var _SettingsManager_instances, updateHighlightSettings_fn, updateHighlightToPreset_fn; + var SettingsManager = class extends WindowSettings { + /** Constructor for the SettingsManager class + * @param {string} name - The name of the userscript + * @param {string} version - The version of the userscript + * @param {Object} userSettings - The user settings as an object + * @since 0.91.11 + */ + constructor(name2, version2, userSettings2) { + var _a; + super(name2, version2); + __privateAdd(this, _SettingsManager_instances); + this.userSettings = userSettings2; + (_a = this.userSettings).flags ?? (_a.flags = []); + this.userSettingsOld = structuredClone(this.userSettings); + this.userSettingsSaveLocation = "bmUserSettings"; + this.updateFrequency = 5e3; + this.lastUpdateTime = 0; + setInterval(this.updateUserStorage.bind(this), this.updateFrequency); + } + /** Updates the user settings in userscript storage + * @since 0.91.39 + */ + async updateUserStorage() { + const userSettingsCurrent = JSON.stringify(this.userSettings); + const userSettingsOld = JSON.stringify(this.userSettingsOld); + if (userSettingsCurrent != userSettingsOld && Date.now() - this.lastUpdateTime > this.updateFrequency) { + await GM.setValue(this.userSettingsSaveLocation, userSettingsCurrent); + this.userSettingsOld = structuredClone(this.userSettings); + this.lastUpdateTime = Date.now(); + console.log(userSettingsCurrent); + } + } + /** Toggles a boolean flag to the state that was passed in. + * If no state was passed in, the flag will flip to the opposite state. + * The existence of the flag determines its state. If it exists, it is `true`. + * @param {string} flagName - The name of the flag to toggle + * @param {boolean} [state=undefined] - (Optional) The state to change the flag to + * @since 0.91.60 + */ + toggleFlag(flagName, state = void 0) { + const flagIndex = this.userSettings?.flags?.indexOf(flagName) ?? -1; + if (flagIndex != -1 && state !== true) { + this.userSettings?.flags?.splice(flagIndex, 1); + } else if (flagIndex == -1 && state !== false) { + this.userSettings?.flags?.push(flagName); + } + } + // This is one of the most insane OOP setups I have ever laid my eyes on + /** Builds the "highlight" category of the settings window + * @since 0.91.18 + * @see WindowSettings#buildHighlight + */ + buildHighlight() { + const highlightPresetOff = ''; + const highlightPresetCross = ''; + const storedHighlight = this.userSettings?.highlight ?? [[1, 0, 1], [2, 0, 0], [1, -1, 0], [1, 1, 0], [1, 0, -1]]; + this.window = this.addDiv({ "class": "bm-container" }).addHeader(2, { "textContent": "Pixel Highlight" }).buildElement().addHr().buildElement().addDiv({ "class": "bm-container", "style": "margin-left: 1.5ch;" }).addCheckbox({ "textContent": "Highlight transparent pixels" }, (instance, label, checkbox) => { + checkbox.checked = !this.userSettings?.flags?.includes("hl-noTrans"); + checkbox.onchange = (event) => this.toggleFlag("hl-noTrans", !event.target.checked); + }).buildElement().addP({ "id": "bm-highlight-preset-label", "textContent": "Choose a preset:", "style": "font-weight: 700;" }).buildElement().addDiv({ "class": "bm-flex-center", "role": "group", "aria-labelledby": "bm-highlight-preset-label" }).addDiv({ "class": "bm-highlight-preset-container" }).addSpan({ "textContent": "None" }).buildElement().addButton({ "innerHTML": highlightPresetOff, "aria-label": 'Preset "None"' }, (instance, button) => { + button.onclick = () => __privateMethod(this, _SettingsManager_instances, updateHighlightToPreset_fn).call(this, "None"); + }).buildElement().buildElement().addDiv({ "class": "bm-highlight-preset-container" }).addSpan({ "textContent": "Cross" }).buildElement().addButton({ "innerHTML": highlightPresetCross, "aria-label": 'Preset "Cross Shape"' }, (instance, button) => { + button.onclick = () => __privateMethod(this, _SettingsManager_instances, updateHighlightToPreset_fn).call(this, "Cross"); + }).buildElement().buildElement().addDiv({ "class": "bm-highlight-preset-container" }).addSpan({ "textContent": "X" }).buildElement().addButton({ "innerHTML": highlightPresetCross.replace('d="M1,0H2V1H3V2H2V3H1V2H0V1H1Z"', 'd="M0,0V1H3V0H2V3H3V2H0V3H1V0Z"'), "aria-label": 'Preset "X Shape"' }, (instance, button) => { + button.onclick = () => __privateMethod(this, _SettingsManager_instances, updateHighlightToPreset_fn).call(this, "X"); + }).buildElement().buildElement().addDiv({ "class": "bm-highlight-preset-container" }).addSpan({ "textContent": "Full" }).buildElement().addButton({ "innerHTML": highlightPresetOff.replace("#fff", "#2f4f4f"), "aria-label": 'Preset "Full Template"' }, (instance, button) => { + button.onclick = () => __privateMethod(this, _SettingsManager_instances, updateHighlightToPreset_fn).call(this, "Full"); + }).buildElement().buildElement().buildElement().addP({ "id": "bm-highlight-grid-label", "textContent": "Create a custom pattern:", "style": "font-weight: 700;" }).buildElement().addDiv({ "class": "bm-highlight-grid", "role": "group", "aria-labelledby": "bm-highlight-grid-label" }); + for (let buttonY = -1; buttonY <= 1; buttonY++) { + for (let buttonX = -1; buttonX <= 1; buttonX++) { + const buttonState = storedHighlight[storedHighlight.findIndex(([, x, y]) => x == buttonX && y == buttonY)]?.[0] ?? 0; + let buttonStateName = "Disabled"; + if (buttonState == 1) { + buttonStateName = "Incorrect"; + } else if (buttonState == 2) { + buttonStateName = "Template"; + } + this.window = this.addButton({ + "data-status": buttonStateName, + "aria-label": `Sub-pixel ${buttonStateName.toLowerCase()}` + }, (instance, button) => { + button.onclick = () => __privateMethod(this, _SettingsManager_instances, updateHighlightSettings_fn).call(this, button, [buttonX, buttonY]); + }).buildElement(); + } + } + this.window = this.buildElement().buildElement().buildElement(); + } + /** Build the "template" category of settings window + * @since 0.91.68 + * @see WindowSettings#buildTemplate + */ + buildTemplate() { + this.window = this.addDiv({ "class": "bm-container" }).addHeader(2, { "textContent": "Pixel Highlight" }).buildElement().addHr().buildElement().addDiv({ "class": "bm-container", "style": "margin-left: 1.5ch;" }).addCheckbox({ "textContent": "Template creation should skip transparent tiles" }, (instance, label, checkbox) => { + checkbox.checked = !this.userSettings?.flags?.includes("hl-noSkip"); + checkbox.onchange = (event) => this.toggleFlag("hl-noSkip", !event.target.checked); + }).buildElement().addCheckbox({ "innerHTML": "Experimental: Template creation should aggressively skip transparent tiles" }, (instance, label, checkbox) => { + checkbox.checked = this.userSettings?.flags?.includes("hl-agSkip"); + checkbox.onchange = (event) => this.toggleFlag("hl-agSkip", event.target.checked); + }).buildElement().buildElement().buildElement(); + } + }; + _SettingsManager_instances = new WeakSet(); + /** Updates the display of the highlight buttons in the settings window. + * Additionally, it will update user settings with the new selection. + * @param {HTMLButtonElement} button - The button that was pressed + * @param {Array} coords - The relative coordinates of the button + * @since 0.91.46 + */ + updateHighlightSettings_fn = function(button, coords2) { + button.disabled = true; + const status = button.dataset["status"]; + const userStorageOld = this.userSettings?.highlight ?? [[1, 0, 1], [2, 0, 0], [1, -1, 0], [1, 1, 0], [1, 0, -1]]; + let userStorageChange = [2, 0, 0]; + const userStorageNew = userStorageOld; + switch (status) { + // If the button was in the "Disabled" state + case "Disabled": + button.dataset["status"] = "Incorrect"; + button.ariaLabel = "Sub-pixel incorrect"; + userStorageChange = [1, ...coords2]; + break; + // If the button was in the "Incorrect" state + case "Incorrect": + button.dataset["status"] = "Template"; + button.ariaLabel = "Sub-pixel template"; + userStorageChange = [2, ...coords2]; + break; + // If the button was in the "Template" state + case "Template": + button.dataset["status"] = "Disabled"; + button.ariaLabel = "Sub-pixel disabled"; + userStorageChange = [0, ...coords2]; + break; + } + const indexOfChange = userStorageOld.findIndex(([, x, y]) => x == userStorageChange[1] && y == userStorageChange[2]); + if (userStorageChange[0] != 0) { + if (indexOfChange != -1) { + userStorageNew[indexOfChange] = userStorageChange; + } else { + userStorageNew.push(userStorageChange); + } + } else if (indexOfChange != -1) { + userStorageNew.splice(indexOfChange, 1); + } + this.userSettings["highlight"] = userStorageNew; + button.disabled = false; + }; + updateHighlightToPreset_fn = async function(preset) { + const presetButtons = document.querySelectorAll(".bm-highlight-preset-container button"); + for (const button of presetButtons) { + button.disabled = true; + } + let presetArray = [0, 0, 0, 0, 2, 0, 0, 0, 0]; + switch (preset) { + case "Cross": + presetArray = [0, 1, 0, 1, 2, 1, 0, 1, 0]; + break; + case "X": + presetArray = [1, 0, 1, 0, 2, 0, 1, 0, 1]; + break; + case "Full": + presetArray = [2, 2, 2, 2, 2, 2, 2, 2, 2]; + break; + } + const buttons = document.querySelector(".bm-highlight-grid")?.childNodes ?? []; + for (let buttonIndex = 0; buttonIndex < buttons.length; buttonIndex++) { + const button = buttons[buttonIndex]; + let buttonState = button.dataset["status"]; + buttonState = buttonState != "Disabled" ? buttonState != "Incorrect" ? 2 : 1 : 0; + let buttonStateDelta = presetArray[buttonIndex] - buttonState; + if (buttonStateDelta == 0) { + continue; + } + buttonStateDelta += buttonStateDelta < 0 ? 3 : 0; + button.click(); + if (buttonStateDelta == 2) { + for (let timeWaited = 0; timeWaited < 200; timeWaited += 10) { + if (!button.disabled) { + break; + } + await sleep(10); + } + button.click(); + } + } + for (const button of presetButtons) { + button.disabled = false; + } + }; + + // src/Template.js + var _Template_instances, calculateTotalPixelsFromImageData_fn; + var Template = class { + /** The constructor for the {@link Template} class with enhanced pixel tracking. + * @param {Object} [params={}] - Object containing all optional parameters + * @param {string} [params.displayName='My template'] - The display name of the template + * @param {number} [params.sortID=0] - The sort number of the template for rendering priority + * @param {string} [params.authorID=''] - The user ID of the person who exported the template (prevents sort ID collisions) + * @param {string} [params.url=''] - The URL to the source image + * @param {File} [params.file=null] - The template file (pre-processed File or processed bitmap) + * @param {Array} [params.coords=null] - The coordinates of the top left corner as (tileX, tileY, pixelX, pixelY) + * @param {Object} [params.chunked=null] - The affected chunks of the template, and their template for each chunk as a bitmap + * @param {Object} [params.chunked32={}] - The affected chunks of the template, and their template for each chunk as a Uint32Array + * @param {number} [params.tileSize=1000] - The size of a tile in pixels (assumes square tiles) + * @param {Object} [params.pixelCount={total:0, colors:Map}] - Total number of pixels in the template (calculated automatically during processing) + * @since 0.65.2 + */ + constructor({ + displayName = "My template", + sortID = 0, + authorID = "", + url = "", + file = null, + coords: coords2 = null, + chunked = null, + chunked32 = {}, + tileSize = 1e3 + } = {}) { + __privateAdd(this, _Template_instances); + this.displayName = displayName; + this.sortID = sortID; + this.authorID = authorID; + this.url = url; + this.file = file; + this.coords = coords2; + this.chunked = chunked; + this.chunked32 = chunked32; + this.tileSize = tileSize; + this.pixelCount = { total: 0, colors: /* @__PURE__ */ new Map() }; + this.shouldSkipTransTiles = true; + this.shouldAggSkipTransTiles = false; + } + /** Creates chunks of the template for each tile. + * @param {Number} tileSize - Size of the tile as determined by templateManager + * @param {Object} paletteBM - An collection of Uint32Arrays containing the palette BM uses + * @param {boolean} shouldSkipTransTiles - Should transparent tiles be skipped over when creating the template? + * @param {boolean} shouldAggSkipTransTiles - Should transparent tiles be aggressively skipped over when creating the template? + * @returns {Object} Collection of template bitmaps & buffers organized by tile coordinates + * @since 0.65.4 + */ + async createTemplateTiles(tileSize, paletteBM, shouldSkipTransTiles, shouldAggSkipTransTiles) { + console.log("Template coordinates:", this.coords); + this.shouldSkipTransTiles = shouldSkipTransTiles; + this.shouldAggSkipTransTiles = shouldAggSkipTransTiles; + const shreadSize = 3; + const bitmap = await createImageBitmap(this.file); + const imageWidth = bitmap.width; + const imageHeight = bitmap.height; + this.tileSize = tileSize; + const templateTiles = {}; + const templateTilesBuffers = {}; + const canvas = new OffscreenCanvas(this.tileSize, this.tileSize); + const context = canvas.getContext("2d", { willReadFrequently: true }); + const transCanvas = new OffscreenCanvas(this.tileSize, this.tileSize); + const transContext = transCanvas.getContext("2d", { willReadFrequently: true }); + transContext.globalCompositeOperation = "destination-over"; + canvas.width = imageWidth; + canvas.height = imageHeight; + context.imageSmoothingEnabled = false; + context.drawImage(bitmap, 0, 0); + let timer = Date.now(); + const totalPixelMap = __privateMethod(this, _Template_instances, calculateTotalPixelsFromImageData_fn).call(this, context.getImageData(0, 0, imageWidth, imageHeight), paletteBM); + console.log(`Calculating total pixels took ${(Date.now() - timer) / 1e3} seconds`); + let totalPixels = 0; + const transparentColorID = 0; + for (const [color, total] of totalPixelMap) { + if (color == transparentColorID) { + continue; + } + totalPixels += total; + } + this.pixelCount = { total: totalPixels, colors: totalPixelMap }; + timer = Date.now(); + const canvasMask = new OffscreenCanvas(3, 3); + const contextMask = canvasMask.getContext("2d"); + contextMask.clearRect(0, 0, 3, 3); + contextMask.fillStyle = "white"; + contextMask.fillRect(1, 1, 1, 1); + for (let pixelY = this.coords[3]; pixelY < imageHeight + this.coords[3]; ) { + const drawSizeY = Math.min(this.tileSize - pixelY % this.tileSize, imageHeight - (pixelY - this.coords[3])); + console.log(`Math.min(${this.tileSize} - (${pixelY} % ${this.tileSize}), ${imageHeight} - (${pixelY - this.coords[3]}))`); + for (let pixelX = this.coords[2]; pixelX < imageWidth + this.coords[2]; ) { + console.log(`Pixel X: ${pixelX} +Pixel Y: ${pixelY}`); + const drawSizeX = Math.min(this.tileSize - pixelX % this.tileSize, imageWidth - (pixelX - this.coords[2])); + if (shouldSkipTransTiles) { + const isTemplateTileTransparent = !this.calculateCanvasTransparency({ + bitmap, + bitmapParams: [pixelX - this.coords[2], pixelY - this.coords[3], drawSizeX, drawSizeY], + // Top left X, Top left Y, Width, Height + transCanvas, + transContext + }); + console.log(`Tile contains template: ${!isTemplateTileTransparent}`); + if (isTemplateTileTransparent) { + pixelX += drawSizeX; + continue; + } + } + console.log(`Math.min(${this.tileSize} - (${pixelX} % ${this.tileSize}), ${imageWidth} - (${pixelX - this.coords[2]}))`); + console.log(`Draw Size X: ${drawSizeX} +Draw Size Y: ${drawSizeY}`); + const canvasWidth = drawSizeX * shreadSize; + const canvasHeight = drawSizeY * shreadSize; + canvas.width = canvasWidth; + canvas.height = canvasHeight; + console.log(`Draw X: ${drawSizeX} +Draw Y: ${drawSizeY} +Canvas Width: ${canvasWidth} +Canvas Height: ${canvasHeight}`); + context.imageSmoothingEnabled = false; + console.log(`Getting X ${pixelX}-${pixelX + drawSizeX} +Getting Y ${pixelY}-${pixelY + drawSizeY}`); + context.clearRect(0, 0, canvasWidth, canvasHeight); + context.drawImage( + bitmap, + // Bitmap image to draw + pixelX - this.coords[2], + // Coordinate X to draw *from* + pixelY - this.coords[3], + // Coordinate Y to draw *from* + drawSizeX, + // X width to draw *from* + drawSizeY, + // Y height to draw *from* + 0, + // Coordinate X to draw *at* + 0, + // Coordinate Y to draw *at* + drawSizeX * shreadSize, + // X width to draw *at* + drawSizeY * shreadSize + // Y height to draw *at* + ); + context.save(); + context.globalCompositeOperation = "destination-in"; + console.log(`Should Skip: ${shouldSkipTransTiles}; Should Agg Skip: ${shouldAggSkipTransTiles}`); + context.fillStyle = context.createPattern(canvasMask, "repeat"); + context.fillRect(0, 0, canvasWidth, canvasHeight); + context.restore(); + const imageData = context.getImageData(0, 0, canvasWidth, canvasHeight); + console.log(`Shreaded pixels for ${pixelX}, ${pixelY}`, imageData); + const templateTileName = `${(this.coords[0] + Math.floor(pixelX / 1e3)).toString().padStart(4, "0")},${(this.coords[1] + Math.floor(pixelY / 1e3)).toString().padStart(4, "0")},${(pixelX % 1e3).toString().padStart(3, "0")},${(pixelY % 1e3).toString().padStart(3, "0")}`; + this.chunked32[templateTileName] = new Uint32Array(imageData.data.buffer); + templateTiles[templateTileName] = await createImageBitmap(canvas); + const canvasBlob = await canvas.convertToBlob(); + const canvasBuffer = await canvasBlob.arrayBuffer(); + const canvasBufferBytes = Array.from(new Uint8Array(canvasBuffer)); + templateTilesBuffers[templateTileName] = uint8ToBase64(canvasBufferBytes); + console.log(templateTiles); + pixelX += drawSizeX; + } + pixelY += drawSizeY; + } + console.log(`Parsing template took ${(Date.now() - timer) / 1e3} seconds`); + console.log("Template Tiles: ", templateTiles); + console.log("Template Tiles Buffers: ", templateTilesBuffers); + console.log("Template Tiles Uint32Array: ", this.chunked32); + return { templateTiles, templateTilesBuffers }; + } + /** Detects if the canvas is transparent. + * @param {Object} param - Object that contains the parameters for the function + * @param {ImageBitmap} param.bitmap - The bitmap template image + * @param {Array} param.bitmapParams - The parameters to obtain the template tile image from the bitmap + * @param {OffscreenCanvas | HTMLCanvasElement} param.transCanvas - The canvas to draw to in order to calculate this + * @param {OffscreenCanvasRenderingContext2D} param.transContext - The context for the transparent canvas to draw to + * @return {boolean} Is the canvas transparent? If transparent, then `true` is returned. Otherwise, `false`. + * @since 0.91.75 + */ + calculateCanvasTransparency({ + bitmap, + bitmapParams, + transCanvas, + transContext + }) { + console.log(`Calculating template tile transparency...`); + console.log(`Should Skip: ${this.shouldSkipTransTiles}; Should Agg: ${this.shouldAggSkipTransTiles}`); + const timer = Date.now(); + const duplicationCoordinateArray = [ + [0, 1], + // E.g. move 0 on the x axis, and 1 down on the y axis + [1, 0], + [0, -2], + // E.g. move 0 on the x axis, and 2 up on the y axis + [-2, 0], + [0, 4], + [4, 0], + [0, -8], + [-8, 0], + [0, 16], + [16, 0], + [0, -32], + [-32, 0] + ]; + const transCanvasWidth = bitmapParams[2]; + const transCanvasHeight = bitmapParams[3]; + transCanvas.width = transCanvasWidth; + transCanvas.height = transCanvasHeight; + transContext.clearRect(0, 0, transCanvasWidth, transCanvasHeight); + if (this.shouldAggSkipTransTiles) { + transContext.drawImage( + bitmap, + ...bitmapParams, + // Bitmap image parameters (x, y, width, height) + 0, + 0, + // The coordinate draw the output *at* + 10, + 10 + // The width and height of the output + ); + } else { + transContext.drawImage( + bitmap, + ...bitmapParams, + // Bitmap image parameters (x, y, width, height) + 0, + 0, + // The coordinate draw the output *at* + transCanvasWidth, + transCanvasHeight + // Stretch to canvas (the canvas should already be the same size as the template image) + ); + for (const [relativeX, relativeY] of duplicationCoordinateArray) { + transContext.drawImage( + transCanvas, + // The canvas we are drawing to *is* the source image + 0, + 0, + transCanvasWidth, + transCanvasHeight, + // The entire canvas (as a source image) + relativeX, + relativeY, + transCanvasWidth, + transCanvasHeight + // The output coordinates and size on the same canvas + ); + } + transContext.drawImage( + transCanvas, + // The canvas we are drawing to *is* the source image + 0, + 0, + transCanvasWidth, + transCanvasHeight, + // The entire canvas (as a source image) + 0, + 0, + 10, + 10 + // The output coordinates and size on the same canvas + ); + } + const shunkCanvas = transContext.getImageData(0, 0, 10, 10); + const shunkCanvas32 = new Uint32Array(shunkCanvas.data.buffer); + console.log(`Calculated canvas transparency in ${(Date.now() - timer) / 1e3} seconds.`); + for (const pixel of shunkCanvas32) { + if (!!pixel) { + return true; + } + } + return false; + } + /** Calculates top left coordinate of template. + * It uses `Template.chunked` to update `Template.coords` + * @since 0.88.504 + */ + calculateCoordsFromChunked() { + let topLeftCoord = [Infinity, Infinity, Infinity, Infinity]; + const tileKeys = Object.keys(this.chunked).sort(); + tileKeys.forEach((key, index) => { + const [tileX, tileY, pixelX, pixelY] = key.split(",").map(Number); + if (tileY < topLeftCoord[1] || tileY == topLeftCoord[1] && tileX < topLeftCoord[0]) { + topLeftCoord = [tileX, tileY, pixelX, pixelY]; + } + }); + this.coords = topLeftCoord; + } + }; + _Template_instances = new WeakSet(); + /** Calculates the total pixels for each color for the image. + * + * @param {ImageData} imageData - The pre-shreaded image "casted" onto a canvas + * @param {Object} paletteBM - The palette Blue Marble uses for colors + * @param {Number} paletteTolerance - How close an RGB color has to be in order to be considered a palette color. A tolerance of "3" means the sum of the RGB can be up to 3 away from the actual value. + * @returns {Map} A map where the key is the color ID, and the value is the total pixels for that color ID + * @since 0.88.6 + */ + calculateTotalPixelsFromImageData_fn = function(imageData, paletteBM) { + const buffer32Arr = new Uint32Array(imageData.data.buffer); + const { palette: _, LUT: lookupTable } = paletteBM; + const _colorpalette = /* @__PURE__ */ new Map(); + for (let pixelIndex = 0; pixelIndex < buffer32Arr.length; pixelIndex++) { + const pixel = buffer32Arr[pixelIndex]; + let bestColorID = -2; + if (pixel >>> 24 == 0) { + bestColorID = 0; + } else { + bestColorID = lookupTable.get(pixel) ?? -2; + } + const colorIDcount = _colorpalette.get(bestColorID); + _colorpalette.set(bestColorID, colorIDcount ? colorIDcount + 1 : 1); + } + console.log(_colorpalette); + return _colorpalette; + }; + + // src/confetttiManager.js + var ConfettiManager = class { + /** The constructor for the confetti manager. + * @since 0.88.356 + */ + constructor() { + this.confettiCount = Math.ceil(80 / 1300 * window.innerWidth); + this.colorPalette = colorpalette.slice(1); + } + /** Immedently creates confetti inside the parent element. + * @param {HTMLElement} parentElement - The parent element to create confetti inside of + * @since 0.88.356 + */ + createConfetti(parentElement) { + const confettiContainer = document.createElement("div"); + for (let currentCount = 0; currentCount < this.confettiCount; currentCount++) { + const confettiShard = document.createElement("confetti-piece"); + confettiShard.style.setProperty("--x", `${Math.random() * 100}vw`); + confettiShard.style.setProperty("--delay", `${Math.random() * 2}s`); + confettiShard.style.setProperty("--duration", `${3 + Math.random() * 3}s`); + confettiShard.style.setProperty("--rot", `${Math.random() * 360}deg`); + confettiShard.style.setProperty("--size", `${6 + Math.random() * 6}px`); + confettiShard.style.backgroundColor = `rgb(${this.colorPalette[Math.floor(Math.random() * this.colorPalette.length)].rgb.join(",")})`; + confettiShard.onanimationend = () => { + if (confettiShard.parentNode.childElementCount <= 1) { + confettiShard.parentNode.remove(); + } else { + confettiShard.remove(); + } + }; + confettiContainer.appendChild(confettiShard); + } + parentElement.appendChild(confettiContainer); + } + }; + var BlueMarbleConfettiPiece = class extends HTMLElement { + }; + customElements.define("confetti-piece", BlueMarbleConfettiPiece); + + // src/WindowCredits.js + var WindowCredts = class extends Overlay { + /** Constructor for the Credits window + * @param {string} name - The name of the userscript + * @param {string} version - The version of the userscript + * @since 0.90.9 + * @see {@link Overlay#constructor} for examples + */ + constructor(name2, version2) { + super(name2, version2); + this.window = null; + this.windowID = "bm-window-credits"; + this.windowParent = document.body; + } + /** Spawns a Credits window. + * If another credits window already exists, we DON'T spawn another! + * Parent/child relationships in the DOM structure below are indicated by indentation. + * @since 0.90.9 + */ + buildWindow() { + const ascii = ` +\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 +\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D +\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 +\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D +\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 +\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D + +\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 +\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D +\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 +\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D +\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 +\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D +`; + if (document.querySelector(`#${this.windowID}`)) { + document.querySelector(`#${this.windowID}`).remove(); + return; + } + this.window = this.addDiv({ "id": this.windowID, "class": "bm-window" }, (instance, div) => { + }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Credits"', "data-button-status": "expanded" }, (instance, button) => { + button.onclick = () => instance.handleMinimization(button); + button.ontouchend = () => { + button.click(); + }; + }).buildElement().addDiv().buildElement().addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Credits"' }, (instance, button) => { + button.onclick = () => { + document.querySelector(`#${this.windowID}`)?.remove(); + }; + button.ontouchend = () => { + button.click(); + }; + }).buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container bm-center-vertically" }).addHeader(1, { "textContent": "Credits" }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container bm-scrollable" }).addSpan({ "role": "img", "aria-label": this.name }).addSpan({ "innerHTML": ascii, "class": "bm-ascii", "aria-hidden": "true" }).buildElement().buildElement().addBr().buildElement().addHr().buildElement().addBr().buildElement().addSpan({ "textContent": '"Blue Marble" userscript is made by SwingTheVine.' }).buildElement().addBr().buildElement().addSpan({ "innerHTML": 'The Blue Marble Website is made by crqch.' }).buildElement().addBr().buildElement().addSpan({ "textContent": `The Blue Marble Website used until ${localizeDate(new Date(1756069320 * 1e3))} was made by Camille Daguin.` }).buildElement().addBr().buildElement().addSpan({ "textContent": 'The favicon "Blue Marble" is owned by NASA. (The image of the Earth is owned by NASA)' }).buildElement().addBr().buildElement().addSpan({ "textContent": "Special Thanks:" }).buildElement().addUl().addLi({ "textContent": "Espresso, Meqa, and Robot for moderating SwingTheVine's community." }).buildElement().addLi({ "innerHTML": 'nof, darkness for creating similar userscripts!' }).buildElement().addLi({ "innerHTML": 'Wonda for the Blue Marble banner image!' }).buildElement().addLi({ "innerHTML": 'BullStein, allanf181 for being early beta testers!' }).buildElement().addLi({ "innerHTML": 'guidu_ and Nick-machado for the original "Minimize" Button code!' }).buildElement().addLi({ "innerHTML": 'Nomad and Gustav for the tutorials!' }).buildElement().addLi({ "innerHTML": 'cfp for creating the template overlay that Blue Marble was based on!' }).buildElement().addLi({ "innerHTML": 'Force Network for hosting the telemetry server!' }).buildElement().addLi({ "innerHTML": 'TheBlueCorner for getting me interested in online pixel canvases!' }).buildElement().buildElement().addBr().buildElement().addSpan({ "innerHTML": 'Donators:' }).buildElement().addUl().addLi({ "textContent": "Espresso" }).buildElement().addLi({ "textContent": "BEST FAN" }).buildElement().addLi({ "textContent": "FuchsDresden" }).buildElement().addLi({ "textContent": "Jack" }).buildElement().addLi({ "textContent": "raiken_au" }).buildElement().addLi({ "textContent": "Jacob" }).buildElement().addLi({ "textContent": "StupidOne" }).buildElement().addLi({ "textContent": "2 Anonymous Supporters" }).buildElement().buildElement().buildElement().buildElement().buildElement().buildOverlay(this.windowParent); + this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`); + } + }; + + // src/WindowFilter.js + var _WindowFilter_instances, buildColorList_fn, sortColorList_fn, selectColorList_fn, calculatePixelStatistics_fn; + var WindowFilter = class extends Overlay { + /** Constructor for the color filter window + * @param {*} executor - The executing class + * @since 0.88.329 + * @see {@link Overlay#constructor} + */ + constructor(executor) { + super(executor.name, executor.version); + __privateAdd(this, _WindowFilter_instances); + this.window = null; + this.windowID = "bm-window-filter"; + this.colorListID = "bm-filter-flex"; + this.windowParent = document.body; + this.templateManager = executor.apiManager?.templateManager; + this.eyeOpen = ''; + this.eyeClosed = ''; + const { palette, LUT: _ } = this.templateManager.paletteBM; + this.palette = palette; + this.tilesLoadedTotal = 0; + this.tilesTotal = 0; + this.allPixelsColor = /* @__PURE__ */ new Map(); + this.allPixelsCorrect = /* @__PURE__ */ new Map(); + this.allPixelsCorrectTotal = 0; + this.allPixelsTotal = 0; + this.timeRemaining = 0; + this.timeRemainingLocalized = ""; + this.sortPrimary = "id"; + this.sortSecondary = "ascending"; + this.showUnused = false; + } + /** Spawns a Color Filter window. + * If another color filter window already exists, we DON'T spawn another! + * Parent/child relationships in the DOM structure below are indicated by indentation. + * @since 0.88.149 + */ + buildWindow() { + if (document.querySelector(`#${this.windowID}`)) { + document.querySelector(`#${this.windowID}`).remove(); + return; + } + this.window = this.addDiv({ "id": this.windowID, "class": "bm-window" }, (instance, div) => { + }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Color Filter"', "data-button-status": "expanded" }, (instance, button) => { + button.onclick = () => instance.handleMinimization(button); + button.ontouchend = () => { + button.click(); + }; + }).buildElement().addDiv().buildElement().addDiv({ "class": "bm-flex-center" }).addButton({ "class": "bm-button-circle", "textContent": "\u{1F5D7}", "aria-label": 'Switch to windowed mode for "Color Filter"' }, (instance, button) => { + button.onclick = () => { + document.querySelector(`#${this.windowID}`)?.remove(); + this.buildWindowed(); + }; + button.ontouchend = () => { + button.click(); + }; + }).buildElement().addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Color Filter"' }, (instance, button) => { + button.onclick = () => { + document.querySelector(`#${this.windowID}`)?.remove(); + }; + button.ontouchend = () => { + button.click(); + }; + }).buildElement().buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container bm-center-vertically" }).addHeader(1, { "textContent": "Color Filter" }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container bm-flex-between bm-center-vertically", "style": "gap: 1.5ch;" }).addButton({ "textContent": "Hide All Colors" }, (instance, button) => { + button.onclick = () => __privateMethod(this, _WindowFilter_instances, selectColorList_fn).call(this, false); + }).buildElement().addButton({ "textContent": "Refresh Data" }, (instance, button) => { + button.onclick = () => { + button.disabled = true; + this.updateColorList(); + button.disabled = false; + }; + }).buildElement().addButton({ "textContent": "Show All Colors" }, (instance, button) => { + button.onclick = () => __privateMethod(this, _WindowFilter_instances, selectColorList_fn).call(this, true); + }).buildElement().buildElement().addDiv({ "class": "bm-container bm-scrollable" }).addDiv({ "class": "bm-container", "style": "margin-left: 2.5ch; margin-right: 2.5ch;" }).addDiv({ "class": "bm-container" }).addSpan({ "id": "bm-filter-tile-load", "innerHTML": "Tiles Loaded: 0 / ???" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-filter-tot-correct", "innerHTML": "Correct Pixels: ???" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-filter-tot-total", "innerHTML": "Total Pixels: ???" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-filter-tot-remaining", "innerHTML": "Complete: ??? (???)" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-filter-tot-completed", "innerHTML": "??? ???" }).buildElement().buildElement().addDiv({ "class": "bm-container" }).addP({ "innerHTML": `Press the \u{1F5D7} button to make this window smaller. Colors with the icon ${this.eyeOpen.replace(" { + button.onclick = (event) => { + event.preventDefault(); + const formData = new FormData(document.querySelector(`#${this.windowID} form`)); + const formValues = {}; + for (const [input, value] of formData) { + formValues[input] = value; + } + console.log(`Primary: ${formValues["sortPrimary"]}; Secondary: ${formValues["sortSecondary"]}; Unused: ${formValues["showUnused"] == "on"}`); + __privateMethod(this, _WindowFilter_instances, sortColorList_fn).call(this, formValues["sortPrimary"], formValues["sortSecondary"], formValues["showUnused"] == "on"); + }; + }).buildElement().buildElement().buildElement().buildElement().buildElement().buildElement().buildElement().buildOverlay(this.windowParent); + this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`); + const scrollableContainer = document.querySelector(`#${this.windowID} .bm-container.bm-scrollable`); + __privateMethod(this, _WindowFilter_instances, buildColorList_fn).call(this, scrollableContainer); + __privateMethod(this, _WindowFilter_instances, sortColorList_fn).call(this, this.sortPrimary, this.sortSecondary, this.showUnused); + this.updateInnerHTML("#bm-filter-tile-load", `Tiles Loaded: ${localizeNumber(this.tilesLoadedTotal)} / ${localizeNumber(this.tilesTotal)}`); + this.updateInnerHTML("#bm-filter-tot-correct", `Correct Pixels: ${localizeNumber(this.allPixelsCorrectTotal)}`); + this.updateInnerHTML("#bm-filter-tot-total", `Total Pixels: ${localizeNumber(this.allPixelsTotal)}`); + this.updateInnerHTML("#bm-filter-tot-remaining", `Remaining: ${localizeNumber((this.allPixelsTotal || 0) - (this.allPixelsCorrectTotal || 0))} (${localizePercent(((this.allPixelsTotal || 0) - (this.allPixelsCorrectTotal || 0)) / (this.allPixelsTotal || 1))})`); + this.updateInnerHTML("#bm-filter-tot-completed", `Completed at: `); + } + /** Spawns a windowed Color Filter window. + * If another color filter window already exists, we DON'T spawn another! + * Parent/child relationships in the DOM structure below are indicated by indentation. + * @since 0.90.35 + */ + buildWindowed() { + if (document.querySelector(`#${this.windowID}`)) { + document.querySelector(`#${this.windowID}`).remove(); + return; + } + this.window = this.addDiv({ "id": this.windowID, "class": "bm-window bm-windowed" }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Color Filter"', "data-button-status": "expanded" }, (instance, button) => { + button.onclick = () => { + const windowedColorTotals = document.querySelector("#bm-filter-windowed-color-totals"); + if (windowedColorTotals) { + windowedColorTotals.style.display = button.dataset["buttonStatus"] == "expanded" ? "none" : ""; + } + instance.handleMinimization(button); + }; + button.ontouchend = () => { + button.click(); + }; + }).buildElement().addDiv().addSpan({ "id": "bm-filter-windowed-color-totals", "class": "bm-dragbar-text", "style": "font-weight: 700;" }).buildElement().buildElement().addDiv({ "class": "bm-flex-center" }).addButton({ "class": "bm-button-circle", "textContent": "\u{1F5D6}", "aria-label": 'Switch to fullscreen mode for "Color Filter"' }, (instance, button) => { + button.onclick = () => { + document.querySelector(`#${this.windowID}`)?.remove(); + this.buildWindow(); + }; + button.ontouchend = () => { + button.click(); + }; + }).buildElement().addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Color Filter"' }, (instance, button) => { + button.onclick = () => { + document.querySelector(`#${this.windowID}`)?.remove(); + }; + button.ontouchend = () => { + button.click(); + }; + }).buildElement().buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container bm-center-vertically" }).addHeader(1, { "textContent": "Color Filter" }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container bm-flex-between bm-center-vertically", "style": "gap: 1.5ch;" }).addButton({ "textContent": "None" }, (instance, button) => { + button.onclick = () => __privateMethod(this, _WindowFilter_instances, selectColorList_fn).call(this, false); + }).buildElement().addButton({ "textContent": "Refresh" }, (instance, button) => { + button.onclick = () => { + button.disabled = true; + this.updateColorList(); + button.disabled = false; + }; + }).buildElement().addButton({ "textContent": "All" }, (instance, button) => { + button.onclick = () => __privateMethod(this, _WindowFilter_instances, selectColorList_fn).call(this, true); + }).buildElement().buildElement().addDiv({ "class": "bm-container bm-scrollable" }).buildElement().buildElement().buildElement().buildOverlay(this.windowParent); + this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`); + const scrollableContainer = document.querySelector(`#${this.windowID} .bm-container.bm-scrollable`); + __privateMethod(this, _WindowFilter_instances, buildColorList_fn).call(this, scrollableContainer); + __privateMethod(this, _WindowFilter_instances, sortColorList_fn).call(this, this.sortPrimary, this.sortSecondary, this.showUnused); + } + /** The information about a specific color on the palette. + * @typedef {Object} ColorData + * @property {number | string} colorTotal + * @property {string} colorTotalLocalized + * @property {number | string} colorCorrect + * @property {string} colorCorrectLocalized + * @property {string} colorPercent + * @property {number} colorIncorrect + */ + /** Updates the information inside the colors in the color list. + * If the color list does not exist yet, it returns the color information instead. + * This assumes the information inside each element is the same between fullscreen and windowed mode. + * @since 0.90.60 + * @returns {Object.} + */ + updateColorList() { + __privateMethod(this, _WindowFilter_instances, calculatePixelStatistics_fn).call(this); + const colorList = document.querySelector(`#${this.colorListID}`); + const colorStatistics = {}; + for (const color of this.palette) { + const colorTotal = this.allPixelsColor.get(color.id) ?? 0; + const colorTotalLocalized = localizeNumber(colorTotal); + let colorCorrect = 0; + let colorCorrectLocalized = "0"; + let colorPercent = localizePercent(1); + if (colorTotal != 0) { + colorCorrect = this.allPixelsCorrect.get(color.id) ?? "???"; + if (typeof colorCorrect != "number" && this.tilesLoadedTotal == this.tilesTotal && !!color.id) { + colorCorrect = 0; + } + colorCorrectLocalized = typeof colorCorrect == "string" ? colorCorrect : localizeNumber(colorCorrect); + colorPercent = isNaN(colorCorrect / colorTotal) ? "???" : localizePercent(colorCorrect / colorTotal); + } + const colorIncorrect = parseInt(colorTotal) - parseInt(colorCorrect); + colorStatistics[color.id] = { + colorTotal, + colorTotalLocalized, + colorCorrect, + colorCorrectLocalized, + colorPercent, + colorIncorrect + }; + } + const windowedColorTotals = document.querySelector("#bm-filter-windowed-color-totals"); + if (windowedColorTotals) { + const allCorrect = this.allPixelsCorrectTotal.toString().length > 7 ? this.allPixelsCorrectTotal.toString().slice(0, 2) + "\u2026" + this.allPixelsCorrectTotal.toString().slice(-3) : this.allPixelsCorrectTotal.toString(); + const allTotal = this.allPixelsTotal.toString().length > 7 ? this.allPixelsTotal.toString().slice(0, 2) + "\u2026" + this.allPixelsTotal.toString().slice(-3) : this.allPixelsTotal.toString(); + this.updateInnerHTML("#bm-filter-windowed-color-totals", `${allCorrect}/${allTotal}`, true); + } + if (!colorList) { + return colorStatistics; + } + const colors = Array.from(colorList.children); + for (const color of colors) { + const colorID = parseInt(color.dataset["id"]); + const { + colorCorrect, + colorCorrectLocalized, + colorPercent, + colorTotal, + colorTotalLocalized, + colorIncorrect + } = colorStatistics[colorID]; + color.dataset["correct"] = !Number.isNaN(parseInt(colorCorrect)) ? colorCorrect : "0"; + color.dataset["total"] = colorTotal; + color.dataset["percent"] = colorPercent.slice(-1) == "%" ? colorPercent.slice(0, -1) : "0"; + color.dataset["incorrect"] = colorIncorrect || 0; + const pixelCount = document.querySelector(`#${this.windowID} .bm-filter-color[data-id="${colorID}"] .bm-filter-color-pxl-cnt`); + if (pixelCount) { + pixelCount.textContent = `${colorCorrectLocalized} / ${colorTotalLocalized}`; + } + const pixelDesc = document.querySelector(`#${this.windowID} .bm-filter-color[data-id="${colorID}"] .bm-filter-color-pxl-desc`); + if (pixelDesc) { + pixelDesc.textContent = `${typeof colorIncorrect == "number" && !isNaN(colorIncorrect) ? colorIncorrect : "???"} incorrect pixel${colorIncorrect == 1 ? "" : "s"}. Completed: ${colorPercent}`; + } + } + __privateMethod(this, _WindowFilter_instances, sortColorList_fn).call(this, this.sortPrimary, this.sortSecondary, this.showUnused); + } + }; + _WindowFilter_instances = new WeakSet(); + /** Creates the color list container. + * @param {HTMLElement} parentElement - Parent element to add the color list to as a child + * @since 0.88.222 + */ + buildColorList_fn = function(parentElement) { + const isWindowedMode = parentElement.closest(`#${this.windowID}`)?.classList.contains("bm-windowed"); + console.log(`Is Windowed Mode: ${isWindowedMode}`); + const colorList = new Overlay(this.name, this.version); + colorList.addDiv({ "id": this.colorListID }); + const colorStatistics = this.updateColorList(); + for (const color of this.palette) { + const colorValueHex = "#" + rgbToHex(color.rgb).toUpperCase(); + const lumin = calculateRelativeLuminance(color.rgb); + let textColorForPaletteColorBackground = 1.05 / (lumin + 0.05) > (lumin + 0.05) / 0.05 ? "white" : "black"; + if (!color.id) { + textColorForPaletteColorBackground = "transparent"; + } + const bgEffectForButtons = textColorForPaletteColorBackground == "white" ? "bm-button-hover-white" : "bm-button-hover-black"; + const { + colorCorrect, + colorCorrectLocalized, + colorPercent, + colorTotal, + colorTotalLocalized, + colorIncorrect + } = colorStatistics[color.id]; + const isColorHidden = !!(this.templateManager.shouldFilterColor.get(color.id) || false); + if (isWindowedMode) { + const styleBackgroundStar = `background-size: auto 100%; background-repeat: repeat-x; background-image: url("data:image/svg+xml;utf8,");`; + colorList.addDiv({ + "class": "bm-container bm-filter-color bm-flex-between", + // Dataset + "data-id": color.id, + "data-name": color.name, + "data-premium": +color.premium, + "data-correct": !Number.isNaN(parseInt(colorCorrect)) ? colorCorrect : "0", + "data-total": colorTotal, + "data-percent": colorPercent.slice(-1) == "%" ? colorPercent.slice(0, -1) : "0", + "data-incorrect": colorIncorrect || 0 + }).addDiv({ "class": "bm-filter-container-rgb", "style": `background-color: rgb(${color.rgb?.map((channel) => Number(channel) || 0).join(",")});${color.premium ? styleBackgroundStar : ""}` }).addButton( + { + "class": "bm-button-trans " + bgEffectForButtons, + "data-state": isColorHidden ? "hidden" : "shown", + "aria-label": isColorHidden ? `Show the color ${color.name || ""} on templates.` : `Hide the color ${color.name || ""} on templates.`, + "innerHTML": isColorHidden ? this.eyeClosed.replace(" { + button.onclick = () => { + button.style.textDecoration = "none"; + button.disabled = true; + if (button.dataset["state"] == "shown") { + button.innerHTML = this.eyeClosed.replace(" Number(channel) || 0).join(",")});` }).addButton( + { + "class": "bm-button-trans " + bgEffectForButtons, + "data-state": isColorHidden ? "hidden" : "shown", + "aria-label": isColorHidden ? `Show the color ${color.name || ""} on templates.` : `Hide the color ${color.name || ""} on templates.`, + "innerHTML": isColorHidden ? this.eyeClosed.replace(" { + button.onclick = () => { + button.style.textDecoration = "none"; + button.disabled = true; + if (button.dataset["state"] == "shown") { + button.innerHTML = this.eyeClosed.replace(" { + const indexValue = index.getAttribute("data-" + sortPrimary); + const nextIndexValue = nextIndex.getAttribute("data-" + sortPrimary); + const indexValueNumber = parseFloat(indexValue); + const nextIndexValueNumber = parseFloat(nextIndexValue); + const indexValueNumberIsNumber = !isNaN(indexValueNumber); + const nextIndexValueNumberIsNumber = !isNaN(nextIndexValueNumber); + if (showUnused) { + index.classList.remove("bm-color-hide"); + } else if (!Number(index.getAttribute("data-total"))) { + index.classList.add("bm-color-hide"); + } + if (indexValueNumberIsNumber && nextIndexValueNumberIsNumber) { + return sortSecondary === "ascending" ? indexValueNumber - nextIndexValueNumber : nextIndexValueNumber - indexValueNumber; + } else { + const indexValueString = indexValue.toLowerCase(); + const nextIndexValueString = nextIndexValue.toLowerCase(); + if (indexValueString < nextIndexValueString) return sortSecondary === "ascending" ? -1 : 1; + if (indexValueString > nextIndexValueString) return sortSecondary === "ascending" ? 1 : -1; + return 0; + } + }); + colors.forEach((color) => colorList.appendChild(color)); + }; + /** (Un)selects all colors in the color list that are visible to the user. + * @param {boolean} userWantsUnselect - Does the user want to unselect colors? + * @since 0.88.222 + */ + selectColorList_fn = function(userWantsUnselect) { + const colorList = document.querySelector(`#${this.colorListID}`); + const colors = Array.from(colorList.children); + for (const color of colors) { + if (color.classList?.contains("bm-color-hide")) { + continue; + } + const button = color.querySelector(".bm-filter-container-rgb button"); + if (button.dataset["state"] == "hidden" && !userWantsUnselect) { + continue; + } + if (button.dataset["state"] == "shown" && userWantsUnselect) { + continue; + } + button.click(); + } + }; + /** Calculates all pixel statistics used in the color filter. + * @since 0.90.34 + */ + calculatePixelStatistics_fn = function() { + this.allPixelsTotal = 0; + this.allPixelsCorrectTotal = 0; + this.allPixelsCorrect = /* @__PURE__ */ new Map(); + this.allPixelsColor = /* @__PURE__ */ new Map(); + for (const template of this.templateManager.templatesArray) { + const total = template.pixelCount?.total ?? 0; + this.allPixelsTotal += total ?? 0; + const colors = template.pixelCount?.colors ?? /* @__PURE__ */ new Map(); + for (const [colorID, colorPixels] of colors) { + const _colorPixels = Number(colorPixels) || 0; + const allPixelsColorSoFar = this.allPixelsColor.get(colorID) ?? 0; + this.allPixelsColor.set(colorID, allPixelsColorSoFar + _colorPixels); + } + const correctObject = template.pixelCount?.correct ?? {}; + this.tilesLoadedTotal += Object.keys(correctObject).length; + this.tilesTotal += Object.keys(template.chunked).length; + for (const map of Object.values(correctObject)) { + for (const [colorID, correctPixels] of map) { + const _correctPixels = Number(correctPixels) || 0; + this.allPixelsCorrectTotal += _correctPixels; + const allPixelsCorrectSoFar = this.allPixelsCorrect.get(colorID) ?? 0; + this.allPixelsCorrect.set(colorID, allPixelsCorrectSoFar + _correctPixels); + } + } + } + console.log(`Tiles loaded: ${this.tilesLoadedTotal} / ${this.tilesTotal}`); + if (this.allPixelsCorrectTotal >= this.allPixelsTotal && !!this.allPixelsTotal && this.tilesLoadedTotal == this.tilesTotal) { + const confettiManager = new ConfettiManager(); + confettiManager.createConfetti(document.querySelector(`#${this.windowID}`)); + } + this.timeRemaining = new Date((this.allPixelsTotal - this.allPixelsCorrectTotal) * 30 * 1e3 + Date.now()); + this.timeRemainingLocalized = localizeDate(this.timeRemaining); + }; + // src/WindowWizard.js var _WindowWizard_instances, displaySchemaHealth_fn, displayTemplateList_fn, convertSchema_1_x_x_To_2_x_x_fn; var _WindowWizard = class _WindowWizard extends Overlay { @@ -1887,17 +2818,201 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`); }; var WindowWizard = _WindowWizard; + // src/WindowMain.js + var _WindowMain_instances, buildWindowFilter_fn, coordinateInputPaste_fn; + var WindowMain = class extends Overlay { + /** Constructor for the main Blue Marble window + * @param {string} name - The name of the userscript + * @param {string} version - The version of the userscript + * @since 0.88.326 + * @see {@link Overlay#constructor} + */ + constructor(name2, version2) { + super(name2, version2); + __privateAdd(this, _WindowMain_instances); + this.window = null; + this.windowID = "bm-window-main"; + this.windowParent = document.body; + } + /** Creates the main Blue Marble window. + * Parent/child relationships in the DOM structure below are indicated by indentation. + * @since 0.58.3 + */ + buildWindow() { + if (document.querySelector(`#${this.windowID}`)) { + this.handleDisplayError("Main window already exists!"); + return; + } + this.window = this.addDiv({ "id": this.windowID, "class": "bm-window bm-windowed", "style": "top: 10px; left: unset; right: 75px;" }, (instance, div) => { + }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Blue Marble"', "data-button-status": "expanded" }, (instance, button) => { + button.onclick = () => instance.handleMinimization(button); + button.ontouchend = () => { + button.click(); + }; + }).buildElement().addDiv().buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container" }).addImg({ "class": "bm-favicon", "src": "https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png" }, (instance, img) => { + const date = /* @__PURE__ */ new Date(); + const dayOfTheYear = Math.floor((date.getTime() - new Date(date.getFullYear(), 0, 1)) / (1e3 * 60 * 60 * 24)) + 1; + if (dayOfTheYear == 204) { + img.parentNode.style.position = "relative"; + img.parentNode.innerHTML = img.parentNode.innerHTML + ``; + img.onload = () => { + const confettiManager = new ConfettiManager(); + confettiManager.createConfetti(document.querySelector(`#${this.windowID}`)); + }; + } + }).buildElement().addHeader(1, { "textContent": this.name }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container" }).addSpan({ "id": "bm-user-droplets", "textContent": "Droplets:" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-user-nextlevel", "textContent": "Next level in..." }).buildElement().addBr().buildElement().addSpan({ "textContent": "Charges: " }).addTimer(Date.now(), 1e3, { "style": "font-weight: 700;" }, (instance, timer) => { + instance.apiManager.chargeRefillTimerID = timer.id; + }).buildElement().buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container" }).addDiv({ "class": "bm-container" }).addButton( + { "class": "bm-button-circle bm-button-pin", "style": "margin-top: 0;", "innerHTML": '' }, + (instance, button) => { + button.onclick = () => { + const coords2 = instance.apiManager?.coordsTilePixel; + if (!coords2?.[0]) { + instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); + return; + } + instance.updateInnerHTML("bm-input-tx", coords2?.[0] || ""); + instance.updateInnerHTML("bm-input-ty", coords2?.[1] || ""); + instance.updateInnerHTML("bm-input-px", coords2?.[2] || ""); + instance.updateInnerHTML("bm-input-py", coords2?.[3] || ""); + }; + } + ).buildElement().addInput({ "type": "number", "id": "bm-input-tx", "class": "bm-input-coords", "placeholder": "Tl X", "min": 0, "max": 2047, "step": 1, "required": true }, (instance, input) => { + input.addEventListener("paste", (event) => __privateMethod(this, _WindowMain_instances, coordinateInputPaste_fn).call(this, instance, input, event)); + }).buildElement().addInput({ "type": "number", "id": "bm-input-ty", "class": "bm-input-coords", "placeholder": "Tl Y", "min": 0, "max": 2047, "step": 1, "required": true }, (instance, input) => { + input.addEventListener("paste", (event) => __privateMethod(this, _WindowMain_instances, coordinateInputPaste_fn).call(this, instance, input, event)); + }).buildElement().addInput({ "type": "number", "id": "bm-input-px", "class": "bm-input-coords", "placeholder": "Px X", "min": 0, "max": 2047, "step": 1, "required": true }, (instance, input) => { + input.addEventListener("paste", (event) => __privateMethod(this, _WindowMain_instances, coordinateInputPaste_fn).call(this, instance, input, event)); + }).buildElement().addInput({ "type": "number", "id": "bm-input-py", "class": "bm-input-coords", "placeholder": "Px Y", "min": 0, "max": 2047, "step": 1, "required": true }, (instance, input) => { + input.addEventListener("paste", (event) => __privateMethod(this, _WindowMain_instances, coordinateInputPaste_fn).call(this, instance, input, event)); + }).buildElement().buildElement().addDiv({ "class": "bm-container" }).addInputFile({ "class": "bm-input-file", "textContent": "Upload Template", "accept": "image/png, image/jpeg, image/webp, image/bmp, image/gif" }).buildElement().buildElement().addDiv({ "class": "bm-container bm-flex-between" }).addButton({ "textContent": "Disable", "data-button-status": "shown" }, (instance, button) => { + button.onclick = () => { + button.disabled = true; + if (button.dataset["buttonStatus"] == "shown") { + instance.apiManager?.templateManager?.setTemplatesShouldBeDrawn(false); + button.dataset["buttonStatus"] = "hidden"; + button.textContent = "Enable"; + instance.handleDisplayStatus(`Disabled templates!`); + } else { + instance.apiManager?.templateManager?.setTemplatesShouldBeDrawn(true); + button.dataset["buttonStatus"] = "shown"; + button.textContent = "Disable"; + instance.handleDisplayStatus(`Enabled templates!`); + } + button.disabled = false; + }; + }).buildElement().addButton({ "textContent": "Create" }, (instance, button) => { + button.onclick = () => { + const input = document.querySelector(`#${this.windowID} .bm-input-file`); + const coordTlX = document.querySelector("#bm-input-tx"); + if (!coordTlX.checkValidity()) { + coordTlX.reportValidity(); + instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); + return; + } + const coordTlY = document.querySelector("#bm-input-ty"); + if (!coordTlY.checkValidity()) { + coordTlY.reportValidity(); + instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); + return; + } + const coordPxX = document.querySelector("#bm-input-px"); + if (!coordPxX.checkValidity()) { + coordPxX.reportValidity(); + instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); + return; + } + const coordPxY = document.querySelector("#bm-input-py"); + if (!coordPxY.checkValidity()) { + coordPxY.reportValidity(); + instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); + return; + } + if (!input?.files[0]) { + instance.handleDisplayError(`No file selected!`); + return; + } + instance?.apiManager?.templateManager.createTemplate(input.files[0], input.files[0]?.name.replace(/\.[^/.]+$/, ""), [Number(coordTlX.value), Number(coordTlY.value), Number(coordPxX.value), Number(coordPxY.value)]); + instance.handleDisplayStatus(`Drew to canvas!`); + }; + }).buildElement().addButton({ "textContent": "Filter" }, (instance, button) => { + button.onclick = () => __privateMethod(this, _WindowMain_instances, buildWindowFilter_fn).call(this); + }).buildElement().buildElement().addDiv({ "class": "bm-container" }).addTextarea({ "id": this.outputStatusId, "placeholder": `Status: Sleeping... +Version: ${this.version}`, "readOnly": true }).buildElement().buildElement().addDiv({ "class": "bm-container bm-flex-between", "style": "margin-bottom: 0; flex-direction: column;" }).addDiv({ "class": "bm-flex-between" }).addButton({ "class": "bm-button-circle", "innerHTML": "\u2699\uFE0F", "title": "Settings" }, (instance, button) => { + button.onclick = () => { + instance.settingsManager.buildWindow(); + }; + }).buildElement().addButton({ "class": "bm-button-circle", "innerHTML": "\u{1F9D9}", "title": "Template Wizard" }, (instance, button) => { + button.onclick = () => { + const templateManager2 = instance.apiManager?.templateManager; + const wizard = new WindowWizard(this.name, this.version, templateManager2?.schemaVersion, templateManager2); + wizard.buildWindow(); + }; + }).buildElement().addButton({ "class": "bm-button-circle", "innerHTML": "\u{1F3A8}", "title": "Template Color Converter" }, (instance, button) => { + button.onclick = () => { + window.open("https://pepoafonso.github.io/color_converter_wplace/", "_blank", "noopener noreferrer"); + }; + }).buildElement().addButton({ "class": "bm-button-circle", "innerHTML": "\u{1F310}", "title": "Official Blue Marble Website" }, (instance, button) => { + button.onclick = () => { + window.open("https://bluemarble.lol/", "_blank", "noopener noreferrer"); + }; + }).buildElement().addButton({ "class": "bm-button-circle", "title": "Donate to SwingTheVine", "innerHTML": '' }, (instance, button) => { + button.onclick = () => { + window.open("https://ko-fi.com/swingthevine", "_blank", "noopener noreferrer"); + }; + }).buildElement().addButton({ "class": "bm-button-circle", "innerHTML": "\u{1F91D}", "title": "Credits" }, (instance, button) => { + button.onclick = () => { + const credits = new WindowCredts(this.name, this.version); + credits.buildWindow(); + }; + }).buildElement().buildElement().addSmall({ "textContent": "Made by SwingTheVine", "style": "margin-top: auto;" }).buildElement().buildElement().buildElement().buildElement().buildElement().buildOverlay(this.windowParent); + this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`); + } + }; + _WindowMain_instances = new WeakSet(); + /** Displays a new color filter window. + * This is a helper function that creates a new class instance. + * This might cause a memory leak. I pray that this is not the case... + * @since 0.88.330 + */ + buildWindowFilter_fn = function() { + const windowFilter = new WindowFilter(this); + windowFilter.buildWindow(); + }; + coordinateInputPaste_fn = async function(instance, input, event) { + event.preventDefault(); + const data = await getClipboardData(event); + const coords2 = data.split(/[^a-zA-Z0-9]+/).filter((index) => index).map(Number).filter( + (number) => !isNaN(number) + // Removes NaN `[4]` + ); + if (coords2.length == 2 && input.id == "bm-input-px") { + instance.updateInnerHTML("bm-input-px", coords2?.[0] || ""); + instance.updateInnerHTML("bm-input-py", coords2?.[1] || ""); + } else if (coords2.length == 1) { + instance.updateInnerHTML(input.id, coords2?.[0] || ""); + } else { + instance.updateInnerHTML("bm-input-tx", coords2?.[0] || ""); + instance.updateInnerHTML("bm-input-ty", coords2?.[1] || ""); + instance.updateInnerHTML("bm-input-px", coords2?.[2] || ""); + instance.updateInnerHTML("bm-input-py", coords2?.[3] || ""); + } + }; + // src/templateManager.js var _TemplateManager_instances, loadTemplate_fn, storeTemplates_fn, parseBlueMarble_fn, parseOSU_fn, calculateCorrectPixelsOnTile_And_FilterTile_fn; var TemplateManager = class { /** The constructor for the {@link TemplateManager} class. + * @param {string} name - The name of the userscript + * @param {string} version - The version of the userscript (SemVer as string) * @since 0.55.8 */ - constructor(name2, version2, overlay) { + constructor(name2, version2) { __privateAdd(this, _TemplateManager_instances); this.name = name2; this.version = version2; - this.overlay = overlay; + this.windowMain = null; + this.settingsManager = null; this.schemaVersion = "2.0.0"; this.userID = null; this.encodingBase = "!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~"; @@ -1913,6 +3028,20 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`); this.templatePixelsCorrect = null; this.shouldFilterColor = /* @__PURE__ */ new Map(); } + /** Updates the stored instance of the main window. + * @param {WindowMain} windowMain - The main window instance + * @since 0.91.54 + */ + setWindowMain(windowMain2) { + this.windowMain = windowMain2; + } + /** Updates the stored instance of the SettingsManager. + * @param {SettingsManager} settingsManager - The settings manager instance + * @since 0.91.54 + */ + setSettingsManager(settingsManager2) { + this.settingsManager = settingsManager2; + } /** Creates the JSON object to store templates in * @returns {{ whoami: string, scriptVersion: string, schemaVersion: string, templates: Object }} The JSON object * @since 0.65.4 @@ -1940,7 +3069,7 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`); this.templatesJSON = await this.createJSON(); console.log(`Creating JSON...`); } - this.overlay.handleDisplayStatus(`Creating template at ${coords2.join(", ")}...`); + this.windowMain.handleDisplayStatus(`Creating template at ${coords2.join(", ")}...`); const template = new Template({ displayName: name2, sortID: 0, @@ -1949,7 +3078,10 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`); file: blob, coords: coords2 }); - const { templateTiles, templateTilesBuffers } = await template.createTemplateTiles(this.tileSize, this.paletteBM); + const shouldSkipTransTiles = !this.settingsManager?.userSettings?.flags?.includes("hl-noSkip"); + const shouldAggSkipTransTiles = this.settingsManager?.userSettings?.flags?.includes("hl-agSkip"); + console.log(`Should Skip: ${shouldSkipTransTiles}; Should Agg Skip: ${shouldAggSkipTransTiles}`); + const { templateTiles, templateTilesBuffers } = await template.createTemplateTiles(this.tileSize, this.paletteBM, shouldSkipTransTiles, shouldAggSkipTransTiles); template.chunked = templateTiles; const _pixels = { "total": template.pixelCount.total, "colors": Object.fromEntries(template.pixelCount.colors) }; this.templatesJSON.templates[`${template.sortID} ${template.authorID}`] = { @@ -1965,7 +3097,7 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`); }; this.templatesArray = []; this.templatesArray.push(template); - this.overlay.handleDisplayStatus(`Template created at ${coords2.join(", ")}!`); + this.windowMain.handleDisplayStatus(`Template created at ${coords2.join(", ")}!`); console.log(Object.keys(this.templatesJSON.templates).length); console.log(this.templatesJSON); console.log(this.templatesArray); @@ -2162,12 +3294,12 @@ Canvas Height: ${canvasHeight}`); return matchingTiles.length > 0; }).reduce((sum, template) => sum + (template.pixelCount.total || 0), 0); const pixelCountFormatted = localizeNumber(totalPixels); - this.overlay.handleDisplayStatus( + this.windowMain.handleDisplayStatus( `Displaying ${templateCount} template${templateCount == 1 ? "" : "s"}. Total pixels: ${pixelCountFormatted}` ); } else { - this.overlay.handleDisplayStatus(`Sleeping + this.windowMain.handleDisplayStatus(`Sleeping Version: ${this.version}`); return tileBlob; } @@ -2182,6 +3314,9 @@ Version: ${this.version}`); context.drawImage(tileBitmap, 0, 0, drawSize, drawSize); const tileBeforeTemplates = context.getImageData(0, 0, drawSize, drawSize); const tileBeforeTemplates32 = new Uint32Array(tileBeforeTemplates.data.buffer); + const highlightPattern = this.settingsManager?.userSettings?.highlight || [[2, 0, 0]]; + const highlightPatternIndexZero = highlightPattern?.[0]; + const highlightDisabled = highlightPattern?.length == 1 && highlightPatternIndexZero?.[0] == 2 && highlightPatternIndexZero?.[1] == 0 && highlightPatternIndexZero?.[2] == 0; for (const template of templatesToDraw) { console.log(`Template:`); console.log(template); @@ -2203,7 +3338,9 @@ Version: ${this.version}`); } = __privateMethod(this, _TemplateManager_instances, calculateCorrectPixelsOnTile_And_FilterTile_fn).call(this, { tile: tileBeforeTemplates32, template: templateBeforeFilter32, - templateInfo: [coordXtoDrawAt, coordYtoDrawAt, template.bitmap.width, template.bitmap.height] + templateInfo: [coordXtoDrawAt, coordYtoDrawAt, template.bitmap.width, template.bitmap.height], + highlightPattern, + highlightDisabled }); let pixelsCorrectTotal = 0; const transparentColorID = 0; @@ -2213,7 +3350,7 @@ Version: ${this.version}`); } pixelsCorrectTotal += total; } - if (this.shouldFilterColor.size != 0 || templateHasErased) { + if (this.shouldFilterColor.size != 0 || templateHasErased || !highlightDisabled) { console.log("Colors to filter: ", this.shouldFilterColor); context.drawImage(await createImageBitmap(new ImageData(new Uint8ClampedArray(templateAfterFilter.buffer), template.bitmap.width, template.bitmap.height)), coordXtoDrawAt, coordYtoDrawAt); } @@ -2292,7 +3429,7 @@ There are ${pixelsCorrectTotal} correct pixels.`); const windowWizard = new WindowWizard(this.name, this.version, this.schemaVersion, this); windowWizard.buildWindow(); } else { - this.overlay.handleDisplayError(`Template version ${schemaVersion} is unsupported. + this.windowMain.handleDisplayError(`Template version ${schemaVersion} is unsupported. Use Blue Marble version ${scriptVersion} or load a new template.`); } async function loadSchema({ @@ -2358,17 +3495,22 @@ Use Blue Marble version ${scriptVersion} or load a new template.`); /** Calculates the correct pixels on this tile. * In addition, this function filters colors based on user input. * In addition, this function modifies colors to properly display (#deface). + * In addition, this function modifies incorrect pixels to display highlighting. * This function has multiple purposes only to reduce iterations of scans over every pixel on the template. * @param {Object} params - Object containing all parameters * @param {Uint32Array} params.tile - The tile without templates as a Uint32Array * @param {Uint32Array} params.template - The template without filtering as a Uint32Array * @param {Array} params.templateInfo - Information about template location and size + * @param {Array} params.highlightPattern - The highlight pattern selected by the user + * @param {boolean} params.highlightDisabled - Should highlighting be disabled? * @returns {{correctPixels: Map, filteredTemplate: Uint32Array}} A Map containing the color IDs (keys) and how many correct pixels there are for that color (values) */ calculateCorrectPixelsOnTile_And_FilterTile_fn = function({ tile: tile32, template: template32, - templateInfo: templateInformation + templateInfo: templateInformation, + highlightPattern, + highlightDisabled }) { const pixelSize = this.drawMult; const tileWidth = this.tileSize * pixelSize; @@ -2380,6 +3522,7 @@ Use Blue Marble version ${scriptVersion} or load a new template.`); const templateWidth = templateInformation[2]; const templateHeight = templateInformation[3]; const tolerance = this.paletteTolerance; + const shouldTransparentTilePixelsBeHighlighted = !this.settingsManager?.userSettings?.flags?.includes("hl-noTrans"); const { palette: _, LUT: lookupTable } = this.paletteBM; const _colorpalette = /* @__PURE__ */ new Map(); for (let templateRow = 1; templateRow < templateHeight; templateRow += pixelSize) { @@ -2391,6 +3534,7 @@ Use Blue Marble version ${scriptVersion} or load a new template.`); const templatePixelAlpha = templatePixel >>> 24 & 255; const tilePixelAlpha = tilePixelAbove >>> 24 & 255; const bestTemplateColorID = lookupTable.get(templatePixel) ?? -2; + const bestTileColorID = lookupTable.get(tilePixelAbove) ?? -2; if (this.shouldFilterColor.get(bestTemplateColorID)) { template32[templateRow * templateWidth + templateColumn] = tilePixelAbove; } @@ -2414,6 +3558,16 @@ Use Blue Marble version ${scriptVersion} or load a new template.`); } } } + if (!highlightDisabled && templatePixelAlpha > tolerance && bestTileColorID != bestTemplateColorID) { + if (shouldTransparentTilePixelsBeHighlighted || tilePixelAlpha > tolerance) { + const templatePixelColor = template32[templateRow * templateWidth + templateColumn]; + for (const subpixelPattern of highlightPattern) { + const [subpixelState, subpixelColumnDelta, subpixelRowDelta] = subpixelPattern; + const subpixelColor = subpixelState != 0 ? subpixelState != 1 ? templatePixelColor : 4278190335 : 0; + template32[(templateRow + subpixelRowDelta) * templateWidth + (templateColumn + subpixelColumnDelta)] = subpixelColor; + } + } + } if (bestTemplateColorID == -1 && tilePixelAbove <= tolerance) { const colorIDcount2 = _colorpalette.get(bestTemplateColorID); _colorpalette.set(bestTemplateColorID, colorIDcount2 ? colorIDcount2 + 1 : 1); @@ -2422,7 +3576,6 @@ Use Blue Marble version ${scriptVersion} or load a new template.`); if (templatePixelAlpha <= tolerance || tilePixelAlpha <= tolerance) { continue; } - const bestTileColorID = lookupTable.get(tilePixelAbove) ?? -2; if (bestTileColorID != bestTemplateColorID) { continue; } @@ -2616,700 +3769,6 @@ Did you try clicking the canvas first?`); } }; - // src/confetttiManager.js - var ConfettiManager = class { - /** The constructor for the confetti manager. - * @since 0.88.356 - */ - constructor() { - this.confettiCount = Math.ceil(80 / 1300 * window.innerWidth); - this.colorPalette = colorpalette.slice(1); - } - /** Immedently creates confetti inside the parent element. - * @param {HTMLElement} parentElement - The parent element to create confetti inside of - * @since 0.88.356 - */ - createConfetti(parentElement) { - const confettiContainer = document.createElement("div"); - for (let currentCount = 0; currentCount < this.confettiCount; currentCount++) { - const confettiShard = document.createElement("confetti-piece"); - confettiShard.style.setProperty("--x", `${Math.random() * 100}vw`); - confettiShard.style.setProperty("--delay", `${Math.random() * 2}s`); - confettiShard.style.setProperty("--duration", `${3 + Math.random() * 3}s`); - confettiShard.style.setProperty("--rot", `${Math.random() * 360}deg`); - confettiShard.style.setProperty("--size", `${6 + Math.random() * 6}px`); - confettiShard.style.backgroundColor = `rgb(${this.colorPalette[Math.floor(Math.random() * this.colorPalette.length)].rgb.join(",")})`; - confettiShard.onanimationend = () => { - if (confettiShard.parentNode.childElementCount <= 1) { - confettiShard.parentNode.remove(); - } else { - confettiShard.remove(); - } - }; - confettiContainer.appendChild(confettiShard); - } - parentElement.appendChild(confettiContainer); - } - }; - var BlueMarbleConfettiPiece = class extends HTMLElement { - }; - customElements.define("confetti-piece", BlueMarbleConfettiPiece); - - // src/WindowCredits.js - var WindowCredts = class extends Overlay { - /** Constructor for the Credits window - * @param {string} name - The name of the userscript - * @param {string} version - The version of the userscript - * @since 0.90.9 - * @see {@link Overlay#constructor} for examples - */ - constructor(name2, version2) { - super(name2, version2); - this.window = null; - this.windowID = "bm-window-credits"; - this.windowParent = document.body; - } - /** Spawns a Credits window. - * If another credits window already exists, we DON'T spawn another! - * Parent/child relationships in the DOM structure below are indicated by indentation. - * @since 0.90.9 - */ - buildWindow() { - const ascii = ` -\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 -\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D -\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 -\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D -\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 -\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D - -\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 -\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D -\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 -\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D -\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 -\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D -`; - if (document.querySelector(`#${this.windowID}`)) { - document.querySelector(`#${this.windowID}`).remove(); - return; - } - this.window = this.addDiv({ "id": this.windowID, "class": "bm-window" }, (instance, div) => { - }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Credits"', "data-button-status": "expanded" }, (instance, button) => { - button.onclick = () => instance.handleMinimization(button); - button.ontouchend = () => { - button.click(); - }; - }).buildElement().addDiv().buildElement().addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Credits"' }, (instance, button) => { - button.onclick = () => { - document.querySelector(`#${this.windowID}`)?.remove(); - }; - button.ontouchend = () => { - button.click(); - }; - }).buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container bm-center-vertically" }).addHeader(1, { "textContent": "Credits" }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container bm-scrollable" }).addSpan({ "role": "img", "aria-label": this.name }).addSpan({ "innerHTML": ascii, "class": "bm-ascii", "aria-hidden": "true" }).buildElement().buildElement().addBr().buildElement().addHr().buildElement().addBr().buildElement().addSpan({ "textContent": '"Blue Marble" userscript is made by SwingTheVine.' }).buildElement().addBr().buildElement().addSpan({ "innerHTML": 'The Blue Marble Website is made by crqch.' }).buildElement().addBr().buildElement().addSpan({ "textContent": `The Blue Marble Website used until ${localizeDate(new Date(1756069320 * 1e3))} was made by Camille Daguin.` }).buildElement().addBr().buildElement().addSpan({ "textContent": 'The favicon "Blue Marble" is owned by NASA. (The image of the Earth is owned by NASA)' }).buildElement().addBr().buildElement().addSpan({ "textContent": "Special Thanks:" }).buildElement().addUl().addLi({ "textContent": "Espresso, Meqa, and Robot for moderating SwingTheVine's community." }).buildElement().addLi({ "innerHTML": 'nof, darkness for creating similar userscripts!' }).buildElement().addLi({ "innerHTML": 'Wonda for the Blue Marble banner image!' }).buildElement().addLi({ "innerHTML": 'BullStein, allanf181 for being early beta testers!' }).buildElement().addLi({ "innerHTML": 'guidu_ and Nick-machado for the original "Minimize" Button code!' }).buildElement().addLi({ "innerHTML": 'Nomad and Gustav for the tutorials!' }).buildElement().addLi({ "innerHTML": 'cfp for creating the template overlay that Blue Marble was based on!' }).buildElement().addLi({ "innerHTML": 'Force Network for hosting the telemetry server!' }).buildElement().addLi({ "innerHTML": 'TheBlueCorner for getting me interested in online pixel canvases!' }).buildElement().buildElement().addBr().buildElement().addSpan({ "innerHTML": 'Donators:' }).buildElement().addUl().addLi({ "textContent": "Espresso" }).buildElement().addLi({ "textContent": "BEST FAN" }).buildElement().addLi({ "textContent": "Jack" }).buildElement().addLi({ "textContent": "raiken_au" }).buildElement().addLi({ "textContent": "Jacob" }).buildElement().addLi({ "textContent": "StupidOne" }).buildElement().addLi({ "textContent": "1 Anonymous Supporter" }).buildElement().buildElement().buildElement().buildElement().buildElement().buildOverlay(this.windowParent); - this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`); - } - }; - - // src/WindowFilter.js - var _WindowFilter_instances, buildColorList_fn, sortColorList_fn, selectColorList_fn, calculatePixelStatistics_fn; - var WindowFilter = class extends Overlay { - /** Constructor for the color filter window - * @param {*} executor - The executing class - * @since 0.88.329 - * @see {@link Overlay#constructor} - */ - constructor(executor) { - super(executor.name, executor.version); - __privateAdd(this, _WindowFilter_instances); - this.window = null; - this.windowID = "bm-window-filter"; - this.colorListID = "bm-filter-flex"; - this.windowParent = document.body; - this.templateManager = executor.apiManager?.templateManager; - this.eyeOpen = ''; - this.eyeClosed = ''; - const { palette, LUT: _ } = this.templateManager.paletteBM; - this.palette = palette; - this.tilesLoadedTotal = 0; - this.tilesTotal = 0; - this.allPixelsColor = /* @__PURE__ */ new Map(); - this.allPixelsCorrect = /* @__PURE__ */ new Map(); - this.allPixelsCorrectTotal = 0; - this.allPixelsTotal = 0; - this.timeRemaining = 0; - this.timeRemainingLocalized = ""; - this.sortPrimary = "id"; - this.sortSecondary = "ascending"; - this.showUnused = false; - } - /** Spawns a Color Filter window. - * If another color filter window already exists, we DON'T spawn another! - * Parent/child relationships in the DOM structure below are indicated by indentation. - * @since 0.88.149 - */ - buildWindow() { - if (document.querySelector(`#${this.windowID}`)) { - document.querySelector(`#${this.windowID}`).remove(); - return; - } - this.window = this.addDiv({ "id": this.windowID, "class": "bm-window" }, (instance, div) => { - }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Color Filter"', "data-button-status": "expanded" }, (instance, button) => { - button.onclick = () => instance.handleMinimization(button); - button.ontouchend = () => { - button.click(); - }; - }).buildElement().addDiv().buildElement().addDiv({ "class": "bm-flex-center" }).addButton({ "class": "bm-button-circle", "textContent": "\u{1F5D7}", "aria-label": 'Switch to windowed mode for "Color Filter"' }, (instance, button) => { - button.onclick = () => { - document.querySelector(`#${this.windowID}`)?.remove(); - this.buildWindowed(); - }; - button.ontouchend = () => { - button.click(); - }; - }).buildElement().addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Color Filter"' }, (instance, button) => { - button.onclick = () => { - document.querySelector(`#${this.windowID}`)?.remove(); - }; - button.ontouchend = () => { - button.click(); - }; - }).buildElement().buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container bm-center-vertically" }).addHeader(1, { "textContent": "Color Filter" }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container bm-flex-between bm-center-vertically", "style": "gap: 1.5ch;" }).addButton({ "textContent": "Hide All Colors" }, (instance, button) => { - button.onclick = () => __privateMethod(this, _WindowFilter_instances, selectColorList_fn).call(this, false); - }).buildElement().addButton({ "textContent": "Refresh Data" }, (instance, button) => { - button.onclick = () => { - button.disabled = true; - this.updateColorList(); - button.disabled = false; - }; - }).buildElement().addButton({ "textContent": "Show All Colors" }, (instance, button) => { - button.onclick = () => __privateMethod(this, _WindowFilter_instances, selectColorList_fn).call(this, true); - }).buildElement().buildElement().addDiv({ "class": "bm-container bm-scrollable" }).addDiv({ "class": "bm-container", "style": "margin-left: 2.5ch; margin-right: 2.5ch;" }).addDiv({ "class": "bm-container" }).addSpan({ "id": "bm-filter-tile-load", "innerHTML": "Tiles Loaded: 0 / ???" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-filter-tot-correct", "innerHTML": "Correct Pixels: ???" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-filter-tot-total", "innerHTML": "Total Pixels: ???" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-filter-tot-remaining", "innerHTML": "Complete: ??? (???)" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-filter-tot-completed", "innerHTML": "??? ???" }).buildElement().buildElement().addDiv({ "class": "bm-container" }).addP({ "innerHTML": `Colors with the icon ${this.eyeOpen.replace(" { - button.onclick = (event) => { - event.preventDefault(); - const formData = new FormData(document.querySelector(`#${this.windowID} form`)); - const formValues = {}; - for (const [input, value] of formData) { - formValues[input] = value; - } - console.log(`Primary: ${formValues["sortPrimary"]}; Secondary: ${formValues["sortSecondary"]}; Unused: ${formValues["showUnused"] == "on"}`); - __privateMethod(this, _WindowFilter_instances, sortColorList_fn).call(this, formValues["sortPrimary"], formValues["sortSecondary"], formValues["showUnused"] == "on"); - }; - }).buildElement().buildElement().buildElement().buildElement().buildElement().buildElement().buildElement().buildOverlay(this.windowParent); - this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`); - const scrollableContainer = document.querySelector(`#${this.windowID} .bm-container.bm-scrollable`); - __privateMethod(this, _WindowFilter_instances, buildColorList_fn).call(this, scrollableContainer); - __privateMethod(this, _WindowFilter_instances, sortColorList_fn).call(this, this.sortPrimary, this.sortSecondary, this.showUnused); - this.updateInnerHTML("#bm-filter-tile-load", `Tiles Loaded: ${localizeNumber(this.tilesLoadedTotal)} / ${localizeNumber(this.tilesTotal)}`); - this.updateInnerHTML("#bm-filter-tot-correct", `Correct Pixels: ${localizeNumber(this.allPixelsCorrectTotal)}`); - this.updateInnerHTML("#bm-filter-tot-total", `Total Pixels: ${localizeNumber(this.allPixelsTotal)}`); - this.updateInnerHTML("#bm-filter-tot-remaining", `Remaining: ${localizeNumber((this.allPixelsTotal || 0) - (this.allPixelsCorrectTotal || 0))} (${localizePercent(((this.allPixelsTotal || 0) - (this.allPixelsCorrectTotal || 0)) / (this.allPixelsTotal || 1))})`); - this.updateInnerHTML("#bm-filter-tot-completed", `Completed at: `); - } - /** Spawns a windowed Color Filter window. - * If another color filter window already exists, we DON'T spawn another! - * Parent/child relationships in the DOM structure below are indicated by indentation. - * @since 0.90.35 - */ - buildWindowed() { - if (document.querySelector(`#${this.windowID}`)) { - document.querySelector(`#${this.windowID}`).remove(); - return; - } - this.window = this.addDiv({ "id": this.windowID, "class": "bm-window bm-windowed" }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Color Filter"', "data-button-status": "expanded" }, (instance, button) => { - button.onclick = () => instance.handleMinimization(button); - button.ontouchend = () => { - button.click(); - }; - }).buildElement().addDiv().buildElement().addDiv({ "class": "bm-flex-center" }).addButton({ "class": "bm-button-circle", "textContent": "\u{1F5D6}", "aria-label": 'Switch to fullscreen mode for "Color Filter"' }, (instance, button) => { - button.onclick = () => { - document.querySelector(`#${this.windowID}`)?.remove(); - this.buildWindow(); - }; - button.ontouchend = () => { - button.click(); - }; - }).buildElement().addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Color Filter"' }, (instance, button) => { - button.onclick = () => { - document.querySelector(`#${this.windowID}`)?.remove(); - }; - button.ontouchend = () => { - button.click(); - }; - }).buildElement().buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container bm-center-vertically" }).addHeader(1, { "textContent": "Color Filter" }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container bm-flex-between bm-center-vertically", "style": "gap: 1.5ch;" }).addButton({ "textContent": "None" }, (instance, button) => { - button.onclick = () => __privateMethod(this, _WindowFilter_instances, selectColorList_fn).call(this, false); - }).buildElement().addButton({ "textContent": "Refresh" }, (instance, button) => { - button.onclick = () => { - button.disabled = true; - this.updateColorList(); - button.disabled = false; - }; - }).buildElement().addButton({ "textContent": "All" }, (instance, button) => { - button.onclick = () => __privateMethod(this, _WindowFilter_instances, selectColorList_fn).call(this, true); - }).buildElement().buildElement().addDiv({ "class": "bm-container bm-scrollable" }).buildElement().buildElement().buildElement().buildOverlay(this.windowParent); - this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`); - const scrollableContainer = document.querySelector(`#${this.windowID} .bm-container.bm-scrollable`); - __privateMethod(this, _WindowFilter_instances, buildColorList_fn).call(this, scrollableContainer); - __privateMethod(this, _WindowFilter_instances, sortColorList_fn).call(this, this.sortPrimary, this.sortSecondary, this.showUnused); - } - /** Updates the information inside the colors in the color list. - * If the color list does not exist yet, it returns the color information instead. - * This assumes the information inside each element is the same between fullscreen and windowed mode. - * @since 0.90.60 - * @returns {Object (lumin + 0.05) / 0.05 ? "white" : "black"; - if (!color.id) { - textColorForPaletteColorBackground = "transparent"; - } - const bgEffectForButtons = textColorForPaletteColorBackground == "white" ? "bm-button-hover-white" : "bm-button-hover-black"; - const { - colorCorrect, - colorCorrectLocalized, - colorPercent, - colorTotal, - colorTotalLocalized, - colorIncorrect - } = colorStatistics[color.id]; - const isColorHidden = !!(this.templateManager.shouldFilterColor.get(color.id) || false); - if (isWindowedMode) { - const styleBackgroundStar = `background-size: auto 100%; background-repeat: repeat-x; background-image: url("data:image/svg+xml;utf8,");`; - colorList.addDiv({ - "class": "bm-container bm-filter-color bm-flex-between", - // Dataset - "data-id": color.id, - "data-name": color.name, - "data-premium": +color.premium, - "data-correct": !Number.isNaN(parseInt(colorCorrect)) ? colorCorrect : "0", - "data-total": colorTotal, - "data-percent": colorPercent.slice(-1) == "%" ? colorPercent.slice(0, -1) : "0", - "data-incorrect": colorIncorrect || 0 - }).addDiv({ "class": "bm-filter-container-rgb", "style": `background-color: rgb(${color.rgb?.map((channel) => Number(channel) || 0).join(",")});${color.premium ? styleBackgroundStar : ""}` }).addButton( - { - "class": "bm-button-trans " + bgEffectForButtons, - "data-state": isColorHidden ? "hidden" : "shown", - "aria-label": isColorHidden ? `Show the color ${color.name || ""} on templates.` : `Hide the color ${color.name || ""} on templates.`, - "innerHTML": isColorHidden ? this.eyeClosed.replace(" { - button.onclick = () => { - button.style.textDecoration = "none"; - button.disabled = true; - if (button.dataset["state"] == "shown") { - button.innerHTML = this.eyeClosed.replace(" Number(channel) || 0).join(",")});` }).addButton( - { - "class": "bm-button-trans " + bgEffectForButtons, - "data-state": isColorHidden ? "hidden" : "shown", - "aria-label": isColorHidden ? `Show the color ${color.name || ""} on templates.` : `Hide the color ${color.name || ""} on templates.`, - "innerHTML": isColorHidden ? this.eyeClosed.replace(" { - button.onclick = () => { - button.style.textDecoration = "none"; - button.disabled = true; - if (button.dataset["state"] == "shown") { - button.innerHTML = this.eyeClosed.replace(" { - const indexValue = index.getAttribute("data-" + sortPrimary); - const nextIndexValue = nextIndex.getAttribute("data-" + sortPrimary); - const indexValueNumber = parseFloat(indexValue); - const nextIndexValueNumber = parseFloat(nextIndexValue); - const indexValueNumberIsNumber = !isNaN(indexValueNumber); - const nextIndexValueNumberIsNumber = !isNaN(nextIndexValueNumber); - if (showUnused) { - index.classList.remove("bm-color-hide"); - } else if (!Number(index.getAttribute("data-total"))) { - index.classList.add("bm-color-hide"); - } - if (indexValueNumberIsNumber && nextIndexValueNumberIsNumber) { - return sortSecondary === "ascending" ? indexValueNumber - nextIndexValueNumber : nextIndexValueNumber - indexValueNumber; - } else { - const indexValueString = indexValue.toLowerCase(); - const nextIndexValueString = nextIndexValue.toLowerCase(); - if (indexValueString < nextIndexValueString) return sortSecondary === "ascending" ? -1 : 1; - if (indexValueString > nextIndexValueString) return sortSecondary === "ascending" ? 1 : -1; - return 0; - } - }); - colors.forEach((color) => colorList.appendChild(color)); - }; - /** (Un)selects all colors in the color list that are visible to the user. - * @param {boolean} userWantsUnselect - Does the user want to unselect colors? - * @since 0.88.222 - */ - selectColorList_fn = function(userWantsUnselect) { - const colorList = document.querySelector(`#${this.colorListID}`); - const colors = Array.from(colorList.children); - for (const color of colors) { - if (color.classList?.contains("bm-color-hide")) { - continue; - } - const button = color.querySelector(".bm-filter-container-rgb button"); - if (button.dataset["state"] == "hidden" && !userWantsUnselect) { - continue; - } - if (button.dataset["state"] == "shown" && userWantsUnselect) { - continue; - } - button.click(); - } - }; - /** Calculates all pixel statistics used in the color filter. - * @since 0.90.34 - */ - calculatePixelStatistics_fn = function() { - this.allPixelsTotal = 0; - this.allPixelsCorrectTotal = 0; - this.allPixelsCorrect = /* @__PURE__ */ new Map(); - this.allPixelsColor = /* @__PURE__ */ new Map(); - for (const template of this.templateManager.templatesArray) { - const total = template.pixelCount?.total ?? 0; - this.allPixelsTotal += total ?? 0; - const colors = template.pixelCount?.colors ?? /* @__PURE__ */ new Map(); - for (const [colorID, colorPixels] of colors) { - const _colorPixels = Number(colorPixels) || 0; - const allPixelsColorSoFar = this.allPixelsColor.get(colorID) ?? 0; - this.allPixelsColor.set(colorID, allPixelsColorSoFar + _colorPixels); - } - const correctObject = template.pixelCount?.correct ?? {}; - this.tilesLoadedTotal += Object.keys(correctObject).length; - this.tilesTotal += Object.keys(template.chunked).length; - for (const map of Object.values(correctObject)) { - for (const [colorID, correctPixels] of map) { - const _correctPixels = Number(correctPixels) || 0; - this.allPixelsCorrectTotal += _correctPixels; - const allPixelsCorrectSoFar = this.allPixelsCorrect.get(colorID) ?? 0; - this.allPixelsCorrect.set(colorID, allPixelsCorrectSoFar + _correctPixels); - } - } - } - console.log(`Tiles loaded: ${this.tilesLoadedTotal} / ${this.tilesTotal}`); - if (this.allPixelsCorrectTotal >= this.allPixelsTotal && !!this.allPixelsTotal && this.tilesLoadedTotal == this.tilesTotal) { - const confettiManager = new ConfettiManager(); - confettiManager.createConfetti(document.querySelector(`#${this.windowID}`)); - } - this.timeRemaining = new Date((this.allPixelsTotal - this.allPixelsCorrectTotal) * 30 * 1e3 + Date.now()); - this.timeRemainingLocalized = localizeDate(this.timeRemaining); - }; - - // src/WindowMain.js - var _WindowMain_instances, buildWindowFilter_fn, coordinateInputPaste_fn; - var WindowMain = class extends Overlay { - /** Constructor for the main Blue Marble window - * @param {string} name - The name of the userscript - * @param {string} version - The version of the userscript - * @since 0.88.326 - * @see {@link Overlay#constructor} - */ - constructor(name2, version2) { - super(name2, version2); - __privateAdd(this, _WindowMain_instances); - this.window = null; - this.windowID = "bm-window-main"; - this.windowParent = document.body; - } - /** Creates the main Blue Marble window. - * Parent/child relationships in the DOM structure below are indicated by indentation. - * @since 0.58.3 - */ - buildWindow() { - if (document.querySelector(`#${this.windowID}`)) { - this.handleDisplayError("Main window already exists!"); - return; - } - this.window = this.addDiv({ "id": this.windowID, "class": "bm-window bm-windowed", "style": "top: 10px; left: unset; right: 75px;" }, (instance, div) => { - }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Blue Marble"', "data-button-status": "expanded" }, (instance, button) => { - button.onclick = () => instance.handleMinimization(button); - button.ontouchend = () => { - button.click(); - }; - }).buildElement().addDiv().buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container" }).addImg({ "class": "bm-favicon", "src": "https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png" }, (instance, img) => { - const date = /* @__PURE__ */ new Date(); - const dayOfTheYear = Math.floor((date.getTime() - new Date(date.getFullYear(), 0, 1)) / (1e3 * 60 * 60 * 24)) + 1; - if (dayOfTheYear == 204) { - img.parentNode.style.position = "relative"; - img.parentNode.innerHTML = img.parentNode.innerHTML + ``; - img.onload = () => { - const confettiManager = new ConfettiManager(); - confettiManager.createConfetti(document.querySelector(`#${this.windowID}`)); - }; - } - }).buildElement().addHeader(1, { "textContent": this.name }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container" }).addSpan({ "id": "bm-user-droplets", "textContent": "Droplets:" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-user-nextlevel", "textContent": "Next level in..." }).buildElement().addBr().buildElement().addSpan({ "textContent": "Charges: " }).addTimer(Date.now(), 1e3, { "style": "font-weight: 700;" }, (instance, timer) => { - instance.apiManager.chargeRefillTimerID = timer.id; - }).buildElement().buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container" }).addDiv({ "class": "bm-container" }).addButton( - { "class": "bm-button-circle bm-button-pin", "style": "margin-top: 0;", "innerHTML": '' }, - (instance, button) => { - button.onclick = () => { - const coords2 = instance.apiManager?.coordsTilePixel; - if (!coords2?.[0]) { - instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); - return; - } - instance.updateInnerHTML("bm-input-tx", coords2?.[0] || ""); - instance.updateInnerHTML("bm-input-ty", coords2?.[1] || ""); - instance.updateInnerHTML("bm-input-px", coords2?.[2] || ""); - instance.updateInnerHTML("bm-input-py", coords2?.[3] || ""); - }; - } - ).buildElement().addInput({ "type": "number", "id": "bm-input-tx", "class": "bm-input-coords", "placeholder": "Tl X", "min": 0, "max": 2047, "step": 1, "required": true }, (instance, input) => { - input.addEventListener("paste", (event) => __privateMethod(this, _WindowMain_instances, coordinateInputPaste_fn).call(this, instance, input, event)); - }).buildElement().addInput({ "type": "number", "id": "bm-input-ty", "class": "bm-input-coords", "placeholder": "Tl Y", "min": 0, "max": 2047, "step": 1, "required": true }, (instance, input) => { - input.addEventListener("paste", (event) => __privateMethod(this, _WindowMain_instances, coordinateInputPaste_fn).call(this, instance, input, event)); - }).buildElement().addInput({ "type": "number", "id": "bm-input-px", "class": "bm-input-coords", "placeholder": "Px X", "min": 0, "max": 2047, "step": 1, "required": true }, (instance, input) => { - input.addEventListener("paste", (event) => __privateMethod(this, _WindowMain_instances, coordinateInputPaste_fn).call(this, instance, input, event)); - }).buildElement().addInput({ "type": "number", "id": "bm-input-py", "class": "bm-input-coords", "placeholder": "Px Y", "min": 0, "max": 2047, "step": 1, "required": true }, (instance, input) => { - input.addEventListener("paste", (event) => __privateMethod(this, _WindowMain_instances, coordinateInputPaste_fn).call(this, instance, input, event)); - }).buildElement().buildElement().addDiv({ "class": "bm-container" }).addInputFile({ "class": "bm-input-file", "textContent": "Upload Template", "accept": "image/png, image/jpeg, image/webp, image/bmp, image/gif" }).buildElement().buildElement().addDiv({ "class": "bm-container bm-flex-between" }).addButton({ "textContent": "Disable", "data-button-status": "shown" }, (instance, button) => { - button.onclick = () => { - button.disabled = true; - if (button.dataset["buttonStatus"] == "shown") { - instance.apiManager?.templateManager?.setTemplatesShouldBeDrawn(false); - button.dataset["buttonStatus"] = "hidden"; - button.textContent = "Enable"; - instance.handleDisplayStatus(`Disabled templates!`); - } else { - instance.apiManager?.templateManager?.setTemplatesShouldBeDrawn(true); - button.dataset["buttonStatus"] = "shown"; - button.textContent = "Disable"; - instance.handleDisplayStatus(`Enabled templates!`); - } - button.disabled = false; - }; - }).buildElement().addButton({ "textContent": "Create" }, (instance, button) => { - button.onclick = () => { - const input = document.querySelector(`#${this.windowID} .bm-input-file`); - const coordTlX = document.querySelector("#bm-input-tx"); - if (!coordTlX.checkValidity()) { - coordTlX.reportValidity(); - instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); - return; - } - const coordTlY = document.querySelector("#bm-input-ty"); - if (!coordTlY.checkValidity()) { - coordTlY.reportValidity(); - instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); - return; - } - const coordPxX = document.querySelector("#bm-input-px"); - if (!coordPxX.checkValidity()) { - coordPxX.reportValidity(); - instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); - return; - } - const coordPxY = document.querySelector("#bm-input-py"); - if (!coordPxY.checkValidity()) { - coordPxY.reportValidity(); - instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); - return; - } - if (!input?.files[0]) { - instance.handleDisplayError(`No file selected!`); - return; - } - instance?.apiManager?.templateManager.createTemplate(input.files[0], input.files[0]?.name.replace(/\.[^/.]+$/, ""), [Number(coordTlX.value), Number(coordTlY.value), Number(coordPxX.value), Number(coordPxY.value)]); - instance.handleDisplayStatus(`Drew to canvas!`); - }; - }).buildElement().addButton({ "textContent": "Filter" }, (instance, button) => { - button.onclick = () => __privateMethod(this, _WindowMain_instances, buildWindowFilter_fn).call(this); - }).buildElement().buildElement().addDiv({ "class": "bm-container" }).addTextarea({ "id": this.outputStatusId, "placeholder": `Status: Sleeping... -Version: ${this.version}`, "readOnly": true }).buildElement().buildElement().addDiv({ "class": "bm-container bm-flex-between", "style": "margin-bottom: 0; flex-direction: column;" }).addDiv({ "class": "bm-flex-between" }).addButton({ "class": "bm-button-circle", "innerHTML": "\u{1F9D9}", "title": "Template Wizard" }, (instance, button) => { - button.onclick = () => { - const templateManager2 = instance.apiManager?.templateManager; - const wizard = new WindowWizard(this.name, this.version, templateManager2?.schemaVersion, templateManager2); - wizard.buildWindow(); - }; - }).buildElement().addButton({ "class": "bm-button-circle", "innerHTML": "\u{1F3A8}", "title": "Template Color Converter" }, (instance, button) => { - button.onclick = () => { - window.open("https://pepoafonso.github.io/color_converter_wplace/", "_blank", "noopener noreferrer"); - }; - }).buildElement().addButton({ "class": "bm-button-circle", "innerHTML": "\u{1F310}", "title": "Official Blue Marble Website" }, (instance, button) => { - button.onclick = () => { - window.open("https://bluemarble.lol/", "_blank", "noopener noreferrer"); - }; - }).buildElement().addButton({ "class": "bm-button-circle", "title": "Donate to SwingTheVine", "innerHTML": '' }, (instance, button) => { - button.onclick = () => { - window.open("https://ko-fi.com/swingthevine", "_blank", "noopener noreferrer"); - }; - }).buildElement().addButton({ "class": "bm-button-circle", "innerHTML": "\u{1F91D}", "title": "Credits" }, (instance, button) => { - button.onclick = () => { - const credits = new WindowCredts(this.name, this.version); - credits.buildWindow(); - }; - }).buildElement().buildElement().addSmall({ "textContent": "Made by SwingTheVine", "style": "margin-top: auto;" }).buildElement().buildElement().buildElement().buildElement().buildElement().buildOverlay(this.windowParent); - this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`); - } - }; - _WindowMain_instances = new WeakSet(); - /** Displays a new color filter window. - * This is a helper function that creates a new class instance. - * This might cause a memory leak. I pray that this is not the case... - * @since 0.88.330 - */ - buildWindowFilter_fn = function() { - const windowFilter = new WindowFilter(this); - windowFilter.buildWindow(); - }; - coordinateInputPaste_fn = async function(instance, input, event) { - event.preventDefault(); - const data = await getClipboardData(event); - const coords2 = data.split(/[^a-zA-Z0-9]+/).filter((index) => index).map(Number).filter( - (number) => !isNaN(number) - // Removes NaN `[4]` - ); - if (coords2.length == 2 && input.id == "bm-input-px") { - instance.updateInnerHTML("bm-input-px", coords2?.[0] || ""); - instance.updateInnerHTML("bm-input-py", coords2?.[1] || ""); - } else if (coords2.length == 1) { - instance.updateInnerHTML(input.id, coords2?.[0] || ""); - } else { - instance.updateInnerHTML("bm-input-tx", coords2?.[0] || ""); - instance.updateInnerHTML("bm-input-ty", coords2?.[1] || ""); - instance.updateInnerHTML("bm-input-px", coords2?.[2] || ""); - instance.updateInnerHTML("bm-input-py", coords2?.[3] || ""); - } - }; - // src/WindowTelemetry.js var _WindowTelemetry_instances, setTelemetryValue_fn; var WindowTelemetry = class extends Overlay { @@ -3479,15 +3938,19 @@ Time Since Blink: ${String(Math.floor(elapsed / 6e4)).padStart(2, "0")}:${String document.head?.appendChild(stylesheetLink); } var stylesheetLink; + var userSettings = JSON.parse(GM_getValue("bmUserSettings", "{}")); var observers = new Observers(); var windowMain = new WindowMain(name, version); - var templateManager = new TemplateManager(name, version, windowMain); + var templateManager = new TemplateManager(name, version); var apiManager = new ApiManager(templateManager); + var settingsManager = new SettingsManager(name, version, userSettings); + windowMain.setSettingsManager(settingsManager); windowMain.setApiManager(apiManager); + templateManager.setWindowMain(windowMain); + templateManager.setSettingsManager(settingsManager); var storageTemplates = JSON.parse(GM_getValue("bmTemplates", "{}")); console.log(storageTemplates); templateManager.importJSON(storageTemplates); - var userSettings = JSON.parse(GM_getValue("bmUserSettings", "{}")); console.log(userSettings); console.log(Object.keys(userSettings).length); if (Object.keys(userSettings).length == 0) { diff --git a/dist/BlueMarble-Standalone.user.js b/dist/BlueMarble-Standalone.user.js index c4cfd7b..2fec5be 100644 --- a/dist/BlueMarble-Standalone.user.js +++ b/dist/BlueMarble-Standalone.user.js @@ -2,7 +2,7 @@ // @name Blue Marble // @name:en Blue Marble // @namespace https://github.com/SwingTheVine/ -// @version 0.91.0 +// @version 0.91.102 // @description A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features. // @description:en A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features. // @author SwingTheVine @@ -38,4 +38,4 @@ The "Blue Marble" image is owned by NASA. */ -(()=>{var t=t=>{throw TypeError(t)},e=(e,i,n)=>i.has(e)?t("Cannot add the same private member more than once"):i instanceof WeakSet?i.add(e):i.set(e,n),i=(e,i,n)=>(((e,i)=>{i.has(e)||t("Cannot access private method")})(e,i),n);function n(t){return new Promise(e=>setTimeout(e,t))}function s(t){return(new Intl.NumberFormat).format(t)}function o(t){return new Intl.NumberFormat(void 0,{style:"percent",t:2,i:2}).format(t)}function a(t){return t.toLocaleString(void 0,{o:"long",l:"numeric",h:"2-digit",m:"2-digit",u:"2-digit"})}function r(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}function l(...t){(0,console.log)(...t)}function c(...t){(0,console.error)(...t)}function h(...t){(0,console.warn)(...t)}function m(t,e){if(0===t)return e[0];let i="";const n=e.length;for(;t>0;)i=e[t%n]+i,t=Math.floor(t/n);return i}function d(t,e){let i=0;const n=e.length;for(const s of t){const t=e.indexOf(s);-1==t&&c(`Invalid character '${s}' encountered whilst decoding! Is the decode alphabet/base incorrect?`),i=i*n+t}return i}function u(t){let e="";for(let i=0;i(t/=255)<=.03928?t/12.92:Math.pow((t+.055)/1.055,2.4));return.2126*e[0]+.7152*e[1]+.0722*e[2]}function f(t,e,i){return Array.isArray(t)&&([t,e,i]=t),(1<<24|t<<16|e<<8|i).toString(16).slice(1)}var w,g,y,x,v,M=[{id:0,premium:!1,name:"Transparent",rgb:[0,0,0]},{id:1,premium:!1,name:"Black",rgb:[0,0,0]},{id:2,premium:!1,name:"Dark Gray",rgb:[60,60,60]},{id:3,premium:!1,name:"Gray",rgb:[120,120,120]},{id:4,premium:!1,name:"Light Gray",rgb:[210,210,210]},{id:5,premium:!1,name:"White",rgb:[255,255,255]},{id:6,premium:!1,name:"Deep Red",rgb:[96,0,24]},{id:7,premium:!1,name:"Red",rgb:[237,28,36]},{id:8,premium:!1,name:"Orange",rgb:[255,127,39]},{id:9,premium:!1,name:"Gold",rgb:[246,170,9]},{id:10,premium:!1,name:"Yellow",rgb:[249,221,59]},{id:11,premium:!1,name:"Light Yellow",rgb:[255,250,188]},{id:12,premium:!1,name:"Dark Green",rgb:[14,185,104]},{id:13,premium:!1,name:"Green",rgb:[19,230,123]},{id:14,premium:!1,name:"Light Green",rgb:[135,255,94]},{id:15,premium:!1,name:"Dark Teal",rgb:[12,129,110]},{id:16,premium:!1,name:"Teal",rgb:[16,174,166]},{id:17,premium:!1,name:"Light Teal",rgb:[19,225,190]},{id:18,premium:!1,name:"Dark Blue",rgb:[40,80,158]},{id:19,premium:!1,name:"Blue",rgb:[64,147,228]},{id:20,premium:!1,name:"Cyan",rgb:[96,247,242]},{id:21,premium:!1,name:"Indigo",rgb:[107,80,246]},{id:22,premium:!1,name:"Light Indigo",rgb:[153,177,251]},{id:23,premium:!1,name:"Dark Purple",rgb:[120,12,153]},{id:24,premium:!1,name:"Purple",rgb:[170,56,185]},{id:25,premium:!1,name:"Light Purple",rgb:[224,159,249]},{id:26,premium:!1,name:"Dark Pink",rgb:[203,0,122]},{id:27,premium:!1,name:"Pink",rgb:[236,31,128]},{id:28,premium:!1,name:"Light Pink",rgb:[243,141,169]},{id:29,premium:!1,name:"Dark Brown",rgb:[104,70,52]},{id:30,premium:!1,name:"Brown",rgb:[149,104,42]},{id:31,premium:!1,name:"Beige",rgb:[248,178,119]},{id:32,premium:!0,name:"Medium Gray",rgb:[170,170,170]},{id:33,premium:!0,name:"Dark Red",rgb:[165,14,30]},{id:34,premium:!0,name:"Light Red",rgb:[250,128,114]},{id:35,premium:!0,name:"Dark Orange",rgb:[228,92,26]},{id:36,premium:!0,name:"Light Tan",rgb:[214,181,148]},{id:37,premium:!0,name:"Dark Goldenrod",rgb:[156,132,49]},{id:38,premium:!0,name:"Goldenrod",rgb:[197,173,49]},{id:39,premium:!0,name:"Light Goldenrod",rgb:[232,212,95]},{id:40,premium:!0,name:"Dark Olive",rgb:[74,107,58]},{id:41,premium:!0,name:"Olive",rgb:[90,148,74]},{id:42,premium:!0,name:"Light Olive",rgb:[132,197,115]},{id:43,premium:!0,name:"Dark Cyan",rgb:[15,121,159]},{id:44,premium:!0,name:"Light Cyan",rgb:[187,250,242]},{id:45,premium:!0,name:"Light Blue",rgb:[125,199,255]},{id:46,premium:!0,name:"Dark Indigo",rgb:[77,49,184]},{id:47,premium:!0,name:"Dark Slate Blue",rgb:[74,66,132]},{id:48,premium:!0,name:"Slate Blue",rgb:[122,113,196]},{id:49,premium:!0,name:"Light Slate Blue",rgb:[181,174,241]},{id:50,premium:!0,name:"Light Brown",rgb:[219,164,99]},{id:51,premium:!0,name:"Dark Beige",rgb:[209,128,81]},{id:52,premium:!0,name:"Light Beige",rgb:[255,197,165]},{id:53,premium:!0,name:"Dark Peach",rgb:[155,82,73]},{id:54,premium:!0,name:"Peach",rgb:[209,128,120]},{id:55,premium:!0,name:"Light Peach",rgb:[250,182,164]},{id:56,premium:!0,name:"Dark Tan",rgb:[123,99,82]},{id:57,premium:!0,name:"Tan",rgb:[156,132,107]},{id:58,premium:!0,name:"Dark Slate",rgb:[51,57,65]},{id:59,premium:!0,name:"Slate",rgb:[109,117,141]},{id:60,premium:!0,name:"Light Slate",rgb:[179,185,209]},{id:61,premium:!0,name:"Dark Stone",rgb:[109,100,63]},{id:62,premium:!0,name:"Stone",rgb:[148,140,107]},{id:63,premium:!0,name:"Light Stone",rgb:[205,197,158]}],C=class{constructor({displayName:t="My template",p:i=0,v:n="",url:s="",file:o=null,coords:a=null,M:r=null,C:l={},$:c=1e3}={}){e(this,w),this.displayName=t,this.p=i,this.v=n,this.url=s,this.file=o,this.coords=a,this.M=r,this.C=l,this.$=c,this.T={total:0,colors:new Map}}async S(t,e){const n=await createImageBitmap(this.file),s=n.width,o=n.height;this.$=t;const a={},r={},l=new OffscreenCanvas(this.$,this.$),c=l.getContext("2d",{willReadFrequently:!0});l.width=s,l.height=o,c.imageSmoothingEnabled=!1,c.drawImage(n,0,0);let h=Date.now();const m=i(this,w,g).call(this,c.getImageData(0,0,s,o),e);let d=0;for(const[t,e]of m)0!=t&&(d+=e);this.T={total:d,colors:m},h=Date.now();const b=new OffscreenCanvas(3,3),p=b.getContext("2d");p.clearRect(0,0,3,3),p.fillStyle="white",p.fillRect(1,1,1,1);for(let t=this.coords[3];t{const[n,s,o,a]=e.split(",").map(Number);(s>>24==0?0:s.get(e)??-2;const a=o.get(n);o.set(n,a?a+1:1)}return o};var $,T,S,k,D=class{constructor(t,i){e(this,y),this.name=t,this.version=i,this.O=null,this.B="bm-l",this.I=null,this.H=null,this.A=[]}W(t){this.O=t}P(){return this.A.length>0&&(this.H=this.A.pop()),this}_(t){t?.appendChild(this.I),this.I=null,this.H=null,this.A=[]}U(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"div",{},t)),this}G(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"p",{},t)),this}F(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"small",{},t)),this}R(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"span",{},t)),this}j(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"details",{},t)),this}V(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"summary",{},t)),this}Y(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"img",{},t)),this}q(t,e={},n=()=>{}){return n(this,i(this,y,x).call(this,"h"+t,{},e)),this}J(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"hr",{},t)),this}X(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"br",{},t)),this}Z(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"form",{},t)),this}K(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"fieldset",{},t)),this}tt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"legend",{},t)),this}et(t={},e=()=>{}){const n=i(this,y,x).call(this,"label",{textContent:t.textContent??""});delete t.textContent;const s=i(this,y,x).call(this,"input",{type:"checkbox"},t);return n.insertBefore(s,n.firstChild),this.P(),e(this,n,s),this}it(t={},e=()=>{}){const n=i(this,y,x).call(this,"label",{textContent:t.textContent??"",for:t.id??""});return delete t.textContent,this.P(),e(this,n,i(this,y,x).call(this,"select",{},t)),this}nt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"option",{},t)),this}st(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"ol",{},t)),this}ot(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"ul",{},t)),this}rt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"menu",{},t)),this}lt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"li",{},t)),this}ct(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"table",{},t)),this}ht(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"caption",{},t)),this}dt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"thead",{},t)),this}ut(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"tbody",{},t)),this}bt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"tfoot",{},t)),this}ft(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"tr",{},t)),this}wt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"th",{},t)),this}gt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"td",{},t)),this}yt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"button",{},t)),this}xt(t={},e=()=>{}){const n=t.title??t.textContent??"Help: No info";delete t.textContent,t.title=`Help: ${n}`;const s={textContent:"?",className:"bm-R",onclick:()=>{this.vt(this.B,n)}};return e(this,i(this,y,x).call(this,"button",s,t)),this}Mt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"input",{},t)),this}Ct(t={},e=()=>{}){const n=t.textContent??"";delete t.textContent;const s=i(this,y,x).call(this,"div"),o=i(this,y,x).call(this,"input",{type:"file",tabindex:"-1","aria-hidden":"true"},t);this.P();const a=i(this,y,x).call(this,"button",{textContent:n});return this.P(),this.P(),a.addEventListener("click",()=>{o.click()}),o.addEventListener("change",()=>{a.style.maxWidth=`${a.offsetWidth}px`,o.files.length>0?a.textContent=o.files[0].name:a.textContent=n}),e(this,s,o,a),this}$t(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"textarea",{},t)),this}Tt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"div",{class:"bm-L"},t)),this}St(t=Date.now(),e=500,n={},s=()=>{}){const o="bm-P",a=n?.id||o+"-"+crypto.randomUUID().slice(0,8),r={class:o},l=i(this,y,x).call(this,"time",r,n);return l.id=a,l.dataset.endDate=t,setInterval(()=>{if(!l.isConnected)return;const t=Math.max(l.dataset.endDate-Date.now(),0),e=Math.floor(t/1e3),i=Math.floor(e/3600),n=Math.floor(e%60),s=Math.floor(e%3600/60);l.setAttribute("datetime",`PT${i}H${s}M${n}S`),l.textContent=String(i).padStart(2,"0")+":"+String(s).padStart(2,"0")+":"+String(n).padStart(2,"0")},e),s(this,l),this}vt(t,e,i=!1){const n=document.getElementById(t.replace(/^#/,""));n&&(n instanceof HTMLInputElement?n.value=e:i?n.textContent=e:n.innerHTML=e)}kt(t){if(t.disabled)return;t.disabled=!0,t.style.textDecoration="none";const e=t.closest(".bm-N"),i=t.closest(".bm-L"),n=e.querySelector("h1"),s=e.querySelector(".bm-h");if(e.parentElement.append(e),"expanded"==t.dataset.buttonStatus){s.style.height=s.scrollHeight+"px",e.style.width=e.scrollWidth+"px",s.style.height="0",s.addEventListener("transitionend",function e(){s.style.display="none",t.disabled=!1,t.style.textDecoration="",s.removeEventListener("transitionend",e)});const i=n.cloneNode(!0),o=i.textContent;t.nextElementSibling.appendChild(i),t.textContent="▶",t.dataset.buttonStatus="collapsed",t.ariaLabel=`Unminimize window "${o}"`}else{const n=i.querySelector("h1"),o=n.textContent;n.remove(),s.style.display="",s.style.height="0",e.style.width="",s.style.height=s.scrollHeight+"px",s.addEventListener("transitionend",function e(){s.style.height="",t.disabled=!1,t.style.textDecoration="",s.removeEventListener("transitionend",e)}),t.textContent="▼",t.dataset.buttonStatus="expanded",t.ariaLabel=`Minimize window "${o}"`}}Dt(t,e){const i=document.querySelector(t),n=document.querySelector(e);if(!i||!n)return void this.Lt(`Can not drag! ${i?"":"moveMe"} ${i||n?"":"and "}${n?"":"iMoveThings "}was not found!`);let s,o=!1,a=0,r=null,l=0,c=0,h=0,m=0,d=null;const u=()=>{if(o){const t=Math.abs(l-h),e=Math.abs(c-m);(t>.5||e>.5)&&(l=h,c=m,i.style.transform=`translate(${l}px, ${c}px)`,i.style.left="0px",i.style.top="0px",i.style.right=""),r=requestAnimationFrame(u)}},b=(t,e)=>{o=!0,d=i.getBoundingClientRect(),s=t-d.left,a=e-d.top;const b=window.getComputedStyle(i).transform;if(b&&"none"!==b){const t=new DOMMatrix(b);l=t.m41,c=t.m42}else l=d.left,c=d.top;h=l,m=c,document.body.style.userSelect="none",n.classList.add("bm-F"),document.addEventListener("mousemove",f),document.addEventListener("touchmove",w,{passive:!1}),document.addEventListener("mouseup",p),document.addEventListener("touchend",p),document.addEventListener("touchcancel",p),r&&cancelAnimationFrame(r),u()},p=()=>{o=!1,r&&(cancelAnimationFrame(r),r=null),document.body.style.userSelect="",n.classList.remove("bm-F"),document.removeEventListener("mousemove",f),document.removeEventListener("touchmove",w),document.removeEventListener("mouseup",p),document.removeEventListener("touchend",p),document.removeEventListener("touchcancel",p)},f=t=>{o&&d&&(h=t.clientX-s,m=t.clientY-a)},w=t=>{if(o&&d){const e=t.touches[0];if(!e)return;h=e.clientX-s,m=e.clientY-a,t.preventDefault()}};n.addEventListener("mousedown",function(t){t.preventDefault(),b(t.clientX,t.clientY)}),n.addEventListener("touchstart",function(t){const e=t?.touches?.[0];e&&(b(e.clientX,e.clientY),t.preventDefault())},{passive:!1})}Nt(t){(0,console.info)(`${this.name}: ${t}`),this.vt(this.B,"Status: "+t,!0)}Lt(t){(0,console.error)(`${this.name}: ${t}`),this.vt(this.B,"Error: "+t,!0)}};y=new WeakSet,x=function(t,e={},n={}){const s=document.createElement(t);this.I?(this.H?.appendChild(s),this.A.push(this.H),this.H=s):(this.I=s,this.H=s);for(const[t,n]of Object.entries(e))i(this,y,v).call(this,s,t,n);for(const[t,e]of Object.entries(n))i(this,y,v).call(this,s,t,e);return s},v=function(t,e,i){if("class"==e)t.classList.add(...i.split(/\s+/));else if("for"==e)t.htmlFor=i;else if("tabindex"==e)t.tabIndex=Number(i);else if("readonly"==e)t.readOnly="true"==i||"1"==i;else if("maxlength"==e)t.maxLength=Number(i);else if(e.startsWith("data"))t.dataset[e.slice(5).split("-").map((t,e)=>0==e?t:t[0].toUpperCase()+t.slice(1)).join("")]=i;else if(e.startsWith("aria")){const n=e.slice(5).split("-").map((t,e)=>0==e?t:t[0].toUpperCase()+t.slice(1)).join("");t["aria"+n[0].toUpperCase()+n.slice(1)]=i}else t[e]=i};var L=class extends D{constructor(t,i,n,s=void 0){super(t,i),e(this,$),this.window=null,this.Ot="bm-m",this.Bt=document.body,this.It=JSON.parse(GM_getValue("bmTemplates","{}")),this.scriptVersion=this.It?.scriptVersion,this.schemaVersion=this.It?.schemaVersion,this.Ht=void 0,this.At=n,this.Wt=s}Pt(){if(document.querySelector(`#${this.Ot}`))return void document.querySelector(`#${this.Ot}`).remove();let t="";document.querySelector("#bm-w")||(t=t.concat("z-index: 9001;").trim()),this.window=this.U({id:this.Ot,class:"bm-N",style:t},(t,e)=>{}).Tt().yt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Template Wizard"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.kt(e),e.ontouchend=()=>{e.click()}}).P().U().P().yt({class:"bm-n",textContent:"✖","aria-label":'Close window "Template Wizard"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.Ot}`)?.remove()},e.ontouchend=()=>{e.click()}}).P().P().U({class:"bm-h"}).U({class:"bm-E bm-d"}).q(1,{textContent:"Template Wizard"}).P().P().J().P().U({class:"bm-E"}).q(2,{textContent:"Status"}).P().G({id:"bm-o",textContent:"Loading template storage status..."}).P().P().U({class:"bm-E bm-A"}).q(2,{textContent:"Detected templates:"}).P().P().P().P()._(this.Bt),this.Dt(`#${this.Ot}.bm-N`,`#${this.Ot} .bm-L`),i(this,$,T).call(this),i(this,$,S).call(this)}};$=new WeakSet,T=function(){const t=this.schemaVersion.split(/[-\.\+]/),e=this.At.split(/[-\.\+]/);let n="";t[0]==e[0]?t[1]==e[1]?(n='Template storage health: Healthy!
No futher action required. (Reason: Semantic version matches)',this.Ht="Good"):(n='Template storage health: Poor!
You can still use your template, but some features may not work. It is recommended that you update Blue Marble\'s template storage. (Reason: MINOR version mismatch)',this.Ht="Poor"):t[0]Bad!
It is guaranteed that some features are broken. You might still be able to use the template. It is HIGHLY recommended that you download all templates and update Blue Marble\'s template storage before continuing. (Reason: MAJOR version mismatch)',this.Ht="Bad"):(n='Template storage health: Dead!
Blue Marble can not load the template storage. (Reason: MAJOR version unknown)',this.Ht="Dead");const s=`
If you want to continue using your current templates, then make sure the template storage (schema) is up-to-date.
If you don't want to update the template storage, then downgrade Blue Marble to version ${r(this.scriptVersion)} to continue using your templates.
Alternatively, if you don't care about corrupting the templates listed below, you can fix any issues with the template storage by uploading a new template.`,o=function(){const t=[...document.querySelectorAll("body > div > .hidden")].filter(t=>/version:/i.test(t.textContent));if(t[0]){const e=t[0].textContent?.match(/\d+/);return e?new Date(Number(e[0])):void 0}}();let l=o?a(o):"???";this.vt("#bm-o",`${n}
Your templates were created during Blue Marble version ${r(this.scriptVersion)} with schema version ${r(this.schemaVersion)}.
The current Blue Marble version is ${r(this.version)} and requires schema version ${r(this.At)}.
Wplace was last updated on ${l}.${"Good"!=this.Ht?s:""}`);const c=new D(this.name,this.version);"Dead"!=this.Ht&&(c.U({class:"bm-E bm-x bm-d",style:"gap: 1.5ch;"}),c.yt({textContent:"Download all templates"},(t,e)=>{e.onclick=()=>{e.disabled=!0,this.Wt._t().then(()=>{e.disabled=!1})}}).P()),"Poor"!=this.Ht&&"Bad"!=this.Ht||c.yt({textContent:`Update template storage to ${this.At}`},(t,e)=>{e.onclick=()=>{e.disabled=!0,i(this,$,k).call(this,!0)}}).P(),c.P()._(document.querySelector("#bm-o").parentNode)},S=function(){const t=this.It?.templates;if(Object.keys(t).length>0){const e=document.querySelector(`#${this.Ot} .bm-A`),i=new D(this.name,this.version);i.U({id:"bm-r",class:"bm-E"});for(const e in t){const n=e,o=t[e];if(t.hasOwnProperty(e)){const t=n.split(" "),e=Number(t?.[0]),a=d(t?.[1]||"0",this.Wt.zt),r=o.name||`Template ${e||""}`,l=o?.coords?.split(",").map(Number),c=o.pixels?.total??void 0,h=void 0,m="number"==typeof e?s(e):"???",u="number"==typeof a?s(a):"???",b="number"==typeof c?s(c):"???";i.U({class:"bm-E bm-x"}).U({class:"bm-x",style:"flex-direction: column; gap: 0;"}).U({class:"bm-1",textContent:h||"🖼️"}).P().F({textContent:`#${m}`}).P().P().U({class:"bm-x bm-0"}).q(3,{textContent:r}).P().R({textContent:`Uploaded by user #${u}`}).P().R({textContent:`Coordinates: ${l.join(", ")}`}).P().R({textContent:`Total Pixels: ${b}`}).P().P().P()}}i.P()._(e)}},k=async function(t){if(t){const t=document.querySelector(`#${this.Ot} .bm-h`);t.innerHTML="",new D(this.name,this.version).U({class:"bm-E"}).U({class:"bm-E bm-d"}).q(1,{textContent:"Template Wizard"}).P().P().J().P().U({class:"bm-E"}).q(2,{textContent:"Status"}).P().G({textContent:"Updating template storage. Please wait..."}).P().P().P()._(t)}GM_deleteValue("bmCoords");const e=this.It?.templates;if(Object.keys(e).length>0)for(const[t,i]of Object.entries(e))if(e.hasOwnProperty(t)){const t=new C({displayName:i.name,M:i.tiles});t.L();const e=await this.Wt.Ut(t);await this.Wt.Gt(e,t.displayName,t.coords)}t&&(document.querySelector(`#${this.Ot}`).remove(),new L(this.name,this.version,this.At,this.Wt).Pt())};var N,O,B,I,H=L;N=new WeakSet,O=async function(){GM.setValue("bmTemplates",JSON.stringify(this.Ft))},B=async function(t){const e=t.templates,i=t?.schemaVersion,n=i.split(/[-\.\+]/),s=this.schemaVersion.split(/[-\.\+]/),o=t?.scriptVersion;n[0]==s[0]?(n[1]!=s[1]&&new H(this.name,this.version,this.schemaVersion,this).Pt(),this.Rt=await async function({$:t,jt:i,Rt:n}){if(Object.keys(e).length>0)for(const s in e){const o=s,a=e[s];if(e.hasOwnProperty(s)){const e=o.split(" "),s=Number(e?.[0]),r=e?.[1]||"0",l=a.name||`Template ${s||""}`,c={total:a.pixels?.total,colors:new Map(Object.entries(a.pixels?.colors||{}).map(([t,e])=>[Number(t),e]))},h=a.tiles,m={},d={},u=t*i;for(const t in h)if(h.hasOwnProperty(t)){const e=b(h[t]),i=new Blob([e],{type:"image/png"}),n=await createImageBitmap(i);m[t]=n;const s=new OffscreenCanvas(u,u).getContext("2d");s.drawImage(n,0,0);const o=s.getImageData(0,0,n.width,n.height);d[t]=new Uint32Array(o.data.buffer)}const p=new C({displayName:l,p:s||this.Rt?.length||0,v:r||""});p.T=c,p.M=m,p.C=d,n.push(p)}}return n}({$:this.$,jt:this.jt,Rt:this.Rt})):n[0]>>24&255,w=b>>>24&255,g=m.get(p)??-2;if(this.Xt.get(g)&&(e[i*r+l]=b),-1==g){const t=536870912;this.Xt.get(g)?e[i*r+l]=0:(h/n&1)==(u/n&1)?(e[i*r+l]=t,e[(i-1)*r+(l-1)]=t,e[(i-1)*r+(l+1)]=t,e[(i+1)*r+(l-1)]=t,e[(i+1)*r+(l+1)]=t):(e[i*r+l]=0,e[(i-1)*r+l]=t,e[(i+1)*r+l]=t,e[i*r+(l-1)]=t,e[i*r+(l+1)]=t)}if(-1==g&&b<=c){const t=d.get(g);d.set(g,t?t+1:1);continue}if(f<=c||w<=c)continue;if((m.get(b)??-2)!=g)continue;const y=d.get(g);d.set(g,y?y+1:1)}return{Qt:d,Zt:e}};var A=class{constructor(){this.Kt=Math.ceil(80/1300*window.innerWidth),this.te=M.slice(1)}ee(t){const e=document.createElement("div");for(let t=0;t{t.parentNode.childElementCount<=1?t.parentNode.remove():t.remove()},e.appendChild(t)}t.appendChild(e)}},W=class extends HTMLElement{};customElements.define("confetti-piece",W);var P,_,z,U,G,F,R,j,E,V,Y=class extends D{constructor(t,e){super(t,e),this.window=null,this.Ot="bm-i",this.Bt=document.body}Pt(){document.querySelector(`#${this.Ot}`)?document.querySelector(`#${this.Ot}`).remove():(this.window=this.U({id:this.Ot,class:"bm-N"},(t,e)=>{}).Tt().yt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Credits"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.kt(e),e.ontouchend=()=>{e.click()}}).P().U().P().yt({class:"bm-n",textContent:"✖","aria-label":'Close window "Credits"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.Ot}`)?.remove()},e.ontouchend=()=>{e.click()}}).P().P().U({class:"bm-h"}).U({class:"bm-E bm-d"}).q(1,{textContent:"Credits"}).P().P().J().P().U({class:"bm-E bm-A"}).R({role:"img","aria-label":this.name}).R({innerHTML:"\n██████╗ ██╗ ██╗ ██╗███████╗\n██╔══██╗██║ ██║ ██║██╔════╝\n██████╔╝██║ ██║ ██║█████╗ \n██╔══██╗██║ ██║ ██║██╔══╝ \n██████╔╝███████╗╚██████╔╝███████╗\n╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝\n\n███╗ ███╗ █████╗ ██████╗ ██████╗ ██╗ ███████╗\n████╗ ████║██╔══██╗██╔══██╗██╔══██╗██║ ██╔════╝\n██╔████╔██║███████║██████╔╝██████╔╝██║ █████╗ \n██║╚██╔╝██║██╔══██║██╔══██╗██╔══██╗██║ ██╔══╝ \n██║ ╚═╝ ██║██║ ██║██║ ██║██████╔╝███████╗███████╗\n╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚══════╝\n",class:"bm-Q","aria-hidden":"true"}).P().P().X().P().J().P().X().P().R({textContent:'"Blue Marble" userscript is made by SwingTheVine.'}).P().X().P().R({innerHTML:'The Blue Marble Website is made by crqch.'}).P().X().P().R({textContent:`The Blue Marble Website used until ${a(new Date(175606932e4))} was made by Camille Daguin.`}).P().X().P().R({textContent:'The favicon "Blue Marble" is owned by NASA. (The image of the Earth is owned by NASA)'}).P().X().P().R({textContent:"Special Thanks:"}).P().ot().lt({textContent:"Espresso, Meqa, and Robot for moderating SwingTheVine's community."}).P().lt({innerHTML:'nof, darkness for creating similar userscripts!'}).P().lt({innerHTML:'Wonda for the Blue Marble banner image!'}).P().lt({innerHTML:'BullStein, allanf181 for being early beta testers!'}).P().lt({innerHTML:'guidu_ and Nick-machado for the original "Minimize" Button code!'}).P().lt({innerHTML:'Nomad and Gustav for the tutorials!'}).P().lt({innerHTML:'cfp for creating the template overlay that Blue Marble was based on!'}).P().lt({innerHTML:'Force Network for hosting the telemetry server!'}).P().lt({innerHTML:'TheBlueCorner for getting me interested in online pixel canvases!'}).P().P().X().P().R({innerHTML:'Donators:'}).P().ot().lt({textContent:"Espresso"}).P().lt({textContent:"BEST FAN"}).P().lt({textContent:"Jack"}).P().lt({textContent:"raiken_au"}).P().lt({textContent:"Jacob"}).P().lt({textContent:"StupidOne"}).P().lt({textContent:"1 Anonymous Supporter"}).P().P().P().P().P()._(this.Bt),this.Dt(`#${this.Ot}.bm-N`,`#${this.Ot} .bm-L`))}},q=class extends D{constructor(t){super(t.name,t.version),e(this,P),this.window=null,this.Ot="bm-p",this.ie="bm-y",this.Bt=document.body,this.Wt=t.O?.Wt,this.ne='',this.se='';const{palette:i,N:n}=this.Wt.Jt;this.palette=i,this.oe=0,this.ae=0,this.re=new Map,this.le=new Map,this.ce=0,this.he=0,this.timeRemaining=0,this.me="",this.sortPrimary="id",this.sortSecondary="ascending",this.showUnused=!1}Pt(){if(document.querySelector(`#${this.Ot}`))return void document.querySelector(`#${this.Ot}`).remove();this.window=this.U({id:this.Ot,class:"bm-N"},(t,e)=>{}).Tt().yt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Color Filter"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.kt(e),e.ontouchend=()=>{e.click()}}).P().U().P().U({class:"bm-x"}).yt({class:"bm-n",textContent:"🗗","aria-label":'Switch to windowed mode for "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.Ot}`)?.remove(),this.de()},e.ontouchend=()=>{e.click()}}).P().yt({class:"bm-n",textContent:"✖","aria-label":'Close window "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.Ot}`)?.remove()},e.ontouchend=()=>{e.click()}}).P().P().P().U({class:"bm-h"}).U({class:"bm-E bm-d"}).q(1,{textContent:"Color Filter"}).P().P().J().P().U({class:"bm-E bm-s bm-d",style:"gap: 1.5ch;"}).yt({textContent:"Hide All Colors"},(t,e)=>{e.onclick=()=>i(this,P,U).call(this,!1)}).P().yt({textContent:"Refresh Data"},(t,e)=>{e.onclick=()=>{e.disabled=!0,this.ue(),e.disabled=!1}}).P().yt({textContent:"Show All Colors"},(t,e)=>{e.onclick=()=>i(this,P,U).call(this,!0)}).P().P().U({class:"bm-E bm-A"}).U({class:"bm-E",style:"margin-left: 2.5ch; margin-right: 2.5ch;"}).U({class:"bm-E"}).R({id:"bm-e",innerHTML:"Tiles Loaded: 0 / ???"}).P().X().P().R({id:"bm-9",innerHTML:"Correct Pixels: ???"}).P().X().P().R({id:"bm-f",innerHTML:"Total Pixels: ???"}).P().X().P().R({id:"bm-4",innerHTML:"Complete: ??? (???)"}).P().X().P().R({id:"bm-5",innerHTML:"??? ???"}).P().P().U({class:"bm-E"}).G({innerHTML:`Colors with the icon ${this.ne.replace("{e.onclick=t=>{t.preventDefault();const e=new FormData(document.querySelector(`#${this.Ot} form`)),n={};for(const[t,i]of e)n[t]=i;i(this,P,z).call(this,n.sortPrimary,n.sortSecondary,"on"==n.showUnused)}}).P().P().P().P().P().P().P()._(this.Bt),this.Dt(`#${this.Ot}.bm-N`,`#${this.Ot} .bm-L`);const t=document.querySelector(`#${this.Ot} .bm-E.bm-A`);i(this,P,_).call(this,t),i(this,P,z).call(this,this.sortPrimary,this.sortSecondary,this.showUnused),this.vt("#bm-e",`Tiles Loaded: ${s(this.oe)} / ${s(this.ae)}`),this.vt("#bm-9",`Correct Pixels: ${s(this.ce)}`),this.vt("#bm-f",`Total Pixels: ${s(this.he)}`),this.vt("#bm-4",`Remaining: ${s((this.he||0)-(this.ce||0))} (${o(((this.he||0)-(this.ce||0))/(this.he||1))})`),this.vt("#bm-5",`Completed at: `)}de(){if(document.querySelector(`#${this.Ot}`))return void document.querySelector(`#${this.Ot}`).remove();this.window=this.U({id:this.Ot,class:"bm-N bm-G"}).Tt().yt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Color Filter"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.kt(e),e.ontouchend=()=>{e.click()}}).P().U().P().U({class:"bm-x"}).yt({class:"bm-n",textContent:"🗖","aria-label":'Switch to fullscreen mode for "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.Ot}`)?.remove(),this.Pt()},e.ontouchend=()=>{e.click()}}).P().yt({class:"bm-n",textContent:"✖","aria-label":'Close window "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.Ot}`)?.remove()},e.ontouchend=()=>{e.click()}}).P().P().P().U({class:"bm-h"}).U({class:"bm-E bm-d"}).q(1,{textContent:"Color Filter"}).P().P().J().P().U({class:"bm-E bm-s bm-d",style:"gap: 1.5ch;"}).yt({textContent:"None"},(t,e)=>{e.onclick=()=>i(this,P,U).call(this,!1)}).P().yt({textContent:"Refresh"},(t,e)=>{e.onclick=()=>{e.disabled=!0,this.ue(),e.disabled=!1}}).P().yt({textContent:"All"},(t,e)=>{e.onclick=()=>i(this,P,U).call(this,!0)}).P().P().U({class:"bm-E bm-A"}).P().P().P()._(this.Bt),this.Dt(`#${this.Ot}.bm-N`,`#${this.Ot} .bm-L`);const t=document.querySelector(`#${this.Ot} .bm-E.bm-A`);i(this,P,_).call(this,t),i(this,P,z).call(this,this.sortPrimary,this.sortSecondary,this.showUnused)}ue(){i(this,P,G).call(this);const t=document.querySelector(`#${this.ie}`),e={};for(const t of this.palette){const i=this.re.get(t.id)??0,n=s(i);let a=0,r="0",l=o(1);0!=i&&(a=this.le.get(t.id)??"???","number"!=typeof a&&this.oe==this.ae&&t.id&&(a=0),r="string"==typeof a?a:s(a),l=isNaN(a/i)?"???":o(a/i));const c=parseInt(i)-parseInt(a);e[t.id]={be:i,pe:n,fe:a,we:r,ge:l,ye:c}}if(!t)return e;const n=Array.from(t.children);for(const t of n){const i=parseInt(t.dataset.id),{fe:n,we:s,ge:o,be:a,pe:r,ye:l}=e[i];t.dataset.correct=Number.isNaN(parseInt(n))?"0":n,t.dataset.total=a,t.dataset.percent="%"==o.slice(-1)?o.slice(0,-1):"0",t.dataset.incorrect=l||0;const c=document.querySelector(`#${this.Ot} .bm-t[data-id="${i}"] .bm-6`);c&&(c.textContent=`${s} / ${r}`);const h=document.querySelector(`#${this.Ot} .bm-t[data-id="${i}"] .bm-3`);h&&(h.textContent=`${"number"!=typeof l||isNaN(l)?"???":l} incorrect pixels. Completed: ${o}`)}i(this,P,z).call(this,this.sortPrimary,this.sortSecondary,this.showUnused)}};P=new WeakSet,_=function(t){const e=t.closest(`#${this.Ot}`)?.classList.contains("bm-G"),i=new D(this.name,this.version);i.U({id:this.ie});const n=this.ue();for(const t of this.palette){const s="#"+f(t.rgb).toUpperCase(),o=p(t.rgb);let a=1.05/(o+.05)>(o+.05)/.05?"white":"black";t.id||(a="transparent");const r="white"==a?"bm-b":"bm-c",{fe:l,we:c,ge:h,be:m,pe:d,ye:u}=n[t.id],b=!!this.Wt.Xt.get(t.id);if(e){const e=`background-size: auto 100%; background-repeat: repeat-x; background-image: url("data:image/svg+xml;utf8,");`;i.U({class:"bm-E bm-t bm-s","data-id":t.id,"data-name":t.name,"data-premium":+t.premium,"data-correct":Number.isNaN(parseInt(l))?"0":l,"data-total":m,"data-percent":"%"==h.slice(-1)?h.slice(0,-1):"0","data-incorrect":u||0}).U({class:"bm-7",style:`background-color: rgb(${t.rgb?.map(t=>Number(t)||0).join(",")});${t.premium?e:""}`}).yt({class:"bm-u "+r,"data-state":b?"hidden":"shown","aria-label":b?`Show the color ${t.name||""} on templates.`:`Hide the color ${t.name||""} on templates.`,innerHTML:b?this.se.replace("{i.onclick=()=>{i.style.textDecoration="none",i.disabled=!0,"shown"==i.dataset.state?(i.innerHTML=this.se.replace("Number(t)||0).join(",")});`}).yt({class:"bm-u "+r,"data-state":b?"hidden":"shown","aria-label":b?`Show the color ${t.name||""} on templates.`:`Hide the color ${t.name||""} on templates.`,innerHTML:b?this.se.replace("{i.onclick=()=>{i.style.textDecoration="none",i.disabled=!0,"shown"==i.dataset.state?(i.innerHTML=this.se.replace("{const o=n.getAttribute("data-"+t),a=s.getAttribute("data-"+t),r=parseFloat(o),l=parseFloat(a),c=!isNaN(r),h=!isNaN(l);if(i?n.classList.remove("bm-B"):Number(n.getAttribute("data-total"))||n.classList.add("bm-B"),c&&h)return"ascending"===e?r-l:l-r;{const t=o.toLowerCase(),i=a.toLowerCase();return ti?"ascending"===e?1:-1:0}}),s.forEach(t=>n.appendChild(t))},U=function(t){const e=document.querySelector(`#${this.ie}`),i=Array.from(e.children);for(const e of i){if(e.classList?.contains("bm-B"))continue;const i=e.querySelector(".bm-7 button");("hidden"!=i.dataset.state||t)&&("shown"==i.dataset.state&&t||i.click())}},G=function(){this.he=0,this.ce=0,this.le=new Map,this.re=new Map;for(const t of this.Wt.Rt){const e=t.T?.total??0;this.he+=e??0;const i=t.T?.colors??new Map;for(const[t,e]of i){const i=Number(e)||0,n=this.re.get(t)??0;this.re.set(t,n+i)}const n=t.T?.correct??{};this.oe+=Object.keys(n).length,this.ae+=Object.keys(t.M).length;for(const t of Object.values(n))for(const[e,i]of t){const t=Number(i)||0;this.ce+=t;const n=this.le.get(e)??0;this.le.set(e,n+t)}}this.ce>=this.he&&this.he&&this.oe==this.ae&&(new A).ee(document.querySelector(`#${this.Ot}`)),this.timeRemaining=new Date(30*(this.he-this.ce)*1e3+Date.now()),this.me=a(this.timeRemaining)},F=new WeakSet,R=function(){new q(this).Pt()},j=async function(t,e,i){i.preventDefault();const n=await async function(t){let e="";return t&&(e=t.clipboardData.getData("text/plain")),0!=e.length||(await navigator.clipboard.readText().then(t=>{e=t}).catch(t=>{l("Failed to retrieve clipboard data using navigator! Using fallback methods...")}),0!=e.length||(e=window.clipboardData?.getData("Text"))),e}(i),s=n.split(/[^a-zA-Z0-9]+/).filter(t=>t).map(Number).filter(t=>!isNaN(t));2==s.length&&"bm-H"==e.id?(t.vt("bm-H",s?.[0]||""),t.vt("bm-I",s?.[1]||"")):1==s.length?t.vt(e.id,s?.[0]||""):(t.vt("bm-J",s?.[0]||""),t.vt("bm-K",s?.[1]||""),t.vt("bm-H",s?.[2]||""),t.vt("bm-I",s?.[3]||""))},E=new WeakSet,V=function(t){const e=JSON.parse(GM_getValue("bmUserSettings","{}"));e.telemetry=t,GM.setValue("bmUserSettings",JSON.stringify(e))};var J=GM_info.script.name.toString(),X=GM_info.script.version.toString();!function(t){const e=document.createElement("script");e.setAttribute("bm-S",J),e.setAttribute("bm-O","color: cornflowerblue;"),e.textContent=`(${t})();`,document.documentElement?.appendChild(e),e.remove()}(()=>{const t=document.currentScript,e=t?.getAttribute("bm-S")||"Blue Marble",i=t?.getAttribute("bm-O")||"",n=new Map;window.addEventListener("message",t=>{const{source:s,endpoint:o,blobID:a,blobData:r,blink:l}=t.data;if(Date.now(),"blue-marble"==s&&a&&r&&!o){const t=n.get(a);"function"==typeof t?t(r):h(`%c${e}%c: Attempted to retrieve a blob (%s) from queue, but the blobID was not a function! Skipping...`,i,"",a),n.delete(a)}});const s=window.fetch;window.fetch=async function(...t){const e=await s.apply(this,t),i=e.clone(),o=(t[0]instanceof Request?t[0]?.url:t[0])||"ignore",a=i.headers.get("content-type")||"";if(a.includes("application/json"))i.json().then(t=>{window.postMessage({source:"blue-marble",endpoint:o,jsonData:t},"*")}).catch(t=>{});else if(a.includes("image/")&&!o.includes("openfreemap")&&!o.includes("maps")){const t=Date.now(),e=await i.blob();return new Promise(s=>{const a=crypto.randomUUID();n.set(a,t=>{s(new Response(t,{headers:i.headers,status:i.status,statusText:i.statusText}))}),window.postMessage({source:"blue-marble",endpoint:o,blobID:a,blobData:e,blink:t})}).catch(t=>{Date.now()})}return e}});var Q=`#bm-p p svg{display:inline;height:1em;fill:#fff}#bm-y{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:center;gap:1em 3ch}.bm-t{width:fit-content;max-width:35ch;background-color:#153063e6;border-radius:1em;padding:.5em;gap:1ch;transition:background-color .3s ease}.bm-t:hover,.bm-t:focus-within{background-color:#112855e6}.bm-7{display:block;border:thick double darkslategray;width:fit-content;height:fit-content;padding:1ch}.bm-t[data-id="-2"] .bm-7{background:conic-gradient(#a00,#aa0 16.6%,#0a0,#0aa 50%,#00a 66.6%,#a0a,#a00)}.bm-t[data-id="-1"] .bm-7{background:url('data:image/svg+xml;utf8,') repeat;background-color:transparent!important}.bm-t[data-id="-1"] .bm-7 svg{fill:#fff!important}.bm-t[data-id="0"] .bm-7{background-color:transparent!important}#bm-p .bm-7 button{padding:.75em .5ch}.bm-7 svg{width:4ch}.bm-t>.bm-s{flex-direction:column;align-items:flex-start;gap:0}.bm-t small{font-size:.75em}#bm-p .bm-t.bm-B{display:none}.bm-G #bm-y{flex-direction:column;gap:.25em}.bm-G .bm-t{width:auto;margin:0;padding:0}.bm-G .bm-7{display:flex;width:100%;gap:.5ch;align-items:center;padding:.1em .5ch;border:none;border-radius:1em}#bm-p.bm-G .bm-7 button{padding:.5em .25ch}.bm-G .bm-7 svg{width:3ch}.bm-G .bm-t h2{font-size:.75em}#bm-r{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start}#bm-r>.bm-E{width:100%;justify-content:flex-start;background-color:#153063e6;border-radius:1em;padding:.5em;transition:background-color .3s ease}#bm-r>.bm-E:hover,#bm-r>.bm-E:focus-within{background-color:#112855e6}#bm-r .bm-1{height:100%;font-size:xxx-large}#bm-r .bm-0{flex-direction:column;align-items:flex-start;gap:0}div:has(>confetti-piece){position:absolute;inset:0;overflow:hidden;pointer-events:none}confetti-piece{position:absolute;top:-10px;width:var(--size);height:var(--size);background:currentColor;transform:translate3d(var(--x),-10vh,0) rotate(var(--rot));animation:fall var(--duration) linear var(--delay);will-change:transform;pointer-events:none}@keyframes fall{to{transform:translate3d(var(--x),110vh,0) rotate(calc(var(--rot) + 720deg))}}.bm-screenreader{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.bm-N{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000;transition:all .3s ease,transform 0s;top:75px;left:60px;width:auto;max-height:fit-content;max-width:calc(100% - 135px);font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}.bm-N.bm-G{max-width:300px}.bm-L{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:.5ch;background:url('data:image/svg+xml;utf8,') repeat;cursor:grab;width:100%;height:fit-content}.bm-L.bm-F{cursor:grabbing}.bm-N:has(.bm-L.bm-F){pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.bm-L.bm-F{pointer-events:auto}.bm-M{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle}.bm-N h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}.bm-L h1{font-size:1.2em;user-select:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-shadow:3px 0px rgba(21,48,99,.5),-3px 0px rgba(21,48,99,.5),0px 3px rgba(21,48,99,.5),0px -3px rgba(21,48,99,.5),3px 3px rgba(21,48,99,.5),-3px 3px rgba(21,48,99,.5),3px -3px rgba(21,48,99,.5),-3px -3px rgba(21,48,99,.5)}.bm-L div:has(h1){display:contents}.bm-N h2{display:inline-block;font-size:larger;font-weight:700;vertical-align:middle}.bm-N h3{display:inline-block;font-size:large;font-weight:700}.bm-E.bm-d{width:fit-content;margin-left:auto;margin-right:auto}.bm-E{margin:.5em 0}.bm-N button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}.bm-N button:hover,.bm-N button:focus-visible{background-color:#1061e5}.bm-N button:active,.bm-N button:disabled{background-color:#2e97ff}.bm-N button:disabled{text-decoration:line-through;cursor:not-allowed}.bm-n{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}.bm-N button.bm-u{background-color:unset}.bm-u.bm-b:hover,.bm-u.bm-b:focus{background-color:#ffffff2b}.bm-u.bm-b:active{background-color:#ffffff38}.bm-u.bm-c:hover,.bm-u.bm-c:focus{background-color:#0000002b}.bm-u.bm-c:active{background-color:#00000038}input[type=number].bm-v{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}input[type=number].bm-v::-webkit-outer-spin-button,input[type=number].bm-v::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}div:has(>.bm-D)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bm-D,input[type=file]{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-N select{color:#fff;background-color:#144eb9;border-radius:1em;padding:0 .5ch}.bm-N label:has(input[type=checkbox]){display:flex;width:fit-content;gap:1ch}.bm-N input[type=checkbox]{width:1em}.bm-h{overflow:hidden;transition:height .3s cubic-bezier(.4,0,.2,1)}.bm-N textarea{font-size:small;background-color:#0003;padding:0 .5ch;height:5.25em;width:100%}.bm-N a:not(:has(*)){text-decoration:underline}.bm-N small{font-size:x-small;color:#d3d3d3}.bm-N ul li{list-style:disc;margin-left:5ch}.bm-N .bm-E.bm-A{max-height:calc(80vh - 150px);overflow:auto}.bm-s{display:flex;align-content:center;justify-content:space-between;align-items:center;gap:.5ch}.bm-x{display:flex;align-content:center;justify-content:center;align-items:center;gap:.5ch}.bm-Q{white-space:pre;letter-spacing:0;line-height:1!important;font-size:1.6em;font-family:monospace}.bm-E:not(#bm-w .bm-E){margin:.25em 0}.bm-G h1:not(#bm-w h1){font-size:1em}`;GM_addStyle(Q);var Z,K="@font-face{font-family:'Roboto Mono';font-style:normal;font-weight:400;src:url(data:font/woff2;base64,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)format('woff2');}";K.indexOf("@font-face")+1?GM_addStyle(K):((Z=document.createElement("link")).href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap",Z.rel="preload",Z.as="style",Z.onload=function(){this.onload=null,this.rel="stylesheet"},document.head?.appendChild(Z)),new class{constructor(){this.xe=null,this.ve=null,this.Me="#bm-j"}Ce(t){return this.ve=t,this.xe=new MutationObserver(t=>{for(const e of t)for(const t of e.addedNodes)t instanceof HTMLElement&&t.matches?.(this.Me)}),this}$e(){return this.xe}observe(t,e=!1,i=!1){t.observe(this.ve,{childList:e,subtree:i})}};var tt=new class extends D{constructor(t,i){super(t,i),e(this,F),this.window=null,this.Ot="bm-w",this.Bt=document.body}Pt(){document.querySelector(`#${this.Ot}`)?this.Lt("Main window already exists!"):(this.window=this.U({id:this.Ot,class:"bm-N bm-G",style:"top: 10px; left: unset; right: 75px;"},(t,e)=>{}).Tt().yt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Blue Marble"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.kt(e),e.ontouchend=()=>{e.click()}}).P().U().P().P().U({class:"bm-h"}).U({class:"bm-E"}).Y({class:"bm-M",src:"data:image/png;base64,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"},(t,e)=>{const i=new Date;204==Math.floor((i.getTime()-new Date(i.getFullYear(),0,1))/864e5)+1&&(e.parentNode.style.position="relative",e.parentNode.innerHTML=e.parentNode.innerHTML+'',e.onload=()=>{(new A).ee(document.querySelector(`#${this.Ot}`))})}).P().q(1,{textContent:this.name}).P().P().J().P().U({class:"bm-E"}).R({id:"bm-q",textContent:"Droplets:"}).P().X().P().R({id:"bm-k",textContent:"Next level in..."}).P().X().P().R({textContent:"Charges: "}).St(Date.now(),1e3,{style:"font-weight: 700;"},(t,e)=>{t.O.Te=e.id}).P().P().P().J().P().U({class:"bm-E"}).U({class:"bm-E"}).yt({class:"bm-n bm-C",style:"margin-top: 0;",innerHTML:''},(t,e)=>{e.onclick=()=>{const e=t.O?.Se;e?.[0]?(t.vt("bm-J",e?.[0]||""),t.vt("bm-K",e?.[1]||""),t.vt("bm-H",e?.[2]||""),t.vt("bm-I",e?.[3]||"")):t.Lt("Coordinates are malformed! Did you try clicking on the canvas first?")}}).P().Mt({type:"number",id:"bm-J",class:"bm-v",placeholder:"Tl X",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,F,j).call(this,t,e,n))}).P().Mt({type:"number",id:"bm-K",class:"bm-v",placeholder:"Tl Y",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,F,j).call(this,t,e,n))}).P().Mt({type:"number",id:"bm-H",class:"bm-v",placeholder:"Px X",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,F,j).call(this,t,e,n))}).P().Mt({type:"number",id:"bm-I",class:"bm-v",placeholder:"Px Y",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,F,j).call(this,t,e,n))}).P().P().U({class:"bm-E"}).Ct({class:"bm-D",textContent:"Upload Template",accept:"image/png, image/jpeg, image/webp, image/bmp, image/gif"}).P().P().U({class:"bm-E bm-s"}).yt({textContent:"Disable","data-button-status":"shown"},(t,e)=>{e.onclick=()=>{e.disabled=!0,"shown"==e.dataset.buttonStatus?(t.O?.Wt?.ke(!1),e.dataset.buttonStatus="hidden",e.textContent="Enable",t.Nt("Disabled templates!")):(t.O?.Wt?.ke(!0),e.dataset.buttonStatus="shown",e.textContent="Disable",t.Nt("Enabled templates!")),e.disabled=!1}}).P().yt({textContent:"Create"},(t,e)=>{e.onclick=()=>{const e=document.querySelector(`#${this.Ot} .bm-D`),i=document.querySelector("#bm-J");if(!i.checkValidity())return i.reportValidity(),void t.Lt("Coordinates are malformed! Did you try clicking on the canvas first?");const n=document.querySelector("#bm-K");if(!n.checkValidity())return n.reportValidity(),void t.Lt("Coordinates are malformed! Did you try clicking on the canvas first?");const s=document.querySelector("#bm-H");if(!s.checkValidity())return s.reportValidity(),void t.Lt("Coordinates are malformed! Did you try clicking on the canvas first?");const o=document.querySelector("#bm-I");if(!o.checkValidity())return o.reportValidity(),void t.Lt("Coordinates are malformed! Did you try clicking on the canvas first?");e?.files[0]?(t?.O?.Wt.Gt(e.files[0],e.files[0]?.name.replace(/\.[^/.]+$/,""),[Number(i.value),Number(n.value),Number(s.value),Number(o.value)]),t.Nt("Drew to canvas!")):t.Lt("No file selected!")}}).P().yt({textContent:"Filter"},(t,e)=>{e.onclick=()=>i(this,F,R).call(this)}).P().P().U({class:"bm-E"}).$t({id:this.B,placeholder:`Status: Sleeping...\nVersion: ${this.version}`,readOnly:!0}).P().P().U({class:"bm-E bm-s",style:"margin-bottom: 0; flex-direction: column;"}).U({class:"bm-s"}).yt({class:"bm-n",innerHTML:"🧙",title:"Template Wizard"},(t,e)=>{e.onclick=()=>{const e=t.O?.Wt;new H(this.name,this.version,e?.schemaVersion,e).Pt()}}).P().yt({class:"bm-n",innerHTML:"🎨",title:"Template Color Converter"},(t,e)=>{e.onclick=()=>{window.open("https://pepoafonso.github.io/color_converter_wplace/","_blank","noopener noreferrer")}}).P().yt({class:"bm-n",innerHTML:"🌐",title:"Official Blue Marble Website"},(t,e)=>{e.onclick=()=>{window.open("https://bluemarble.lol/","_blank","noopener noreferrer")}}).P().yt({class:"bm-n",title:"Donate to SwingTheVine",innerHTML:''},(t,e)=>{e.onclick=()=>{window.open("https://ko-fi.com/swingthevine","_blank","noopener noreferrer")}}).P().yt({class:"bm-n",innerHTML:"🤝",title:"Credits"},(t,e)=>{e.onclick=()=>{new Y(this.name,this.version).Pt()}}).P().P().F({textContent:"Made by SwingTheVine",style:"margin-top: auto;"}).P().P().P().P().P()._(this.Bt),this.Dt(`#${this.Ot}.bm-N`,`#${this.Ot} .bm-L`))}}(J,X),et=new class{constructor(t,i,n){e(this,N),this.name=t,this.version=i,this.I=n,this.schemaVersion="2.0.0",this.De=null,this.zt="!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",this.$=1e3,this.jt=3,this.qt=3,this.Jt=function(t){const e=M;e.unshift({id:-1,premium:!1,name:"Erased",rgb:[222,250,206]}),e.unshift({id:-2,premium:!1,name:"Other",rgb:[0,0,0]});const i=new Map;for(const n of e){if(0==n.id||-2==n.id)continue;const e=n.rgb[0],s=n.rgb[1],o=n.rgb[2];for(let a=-t;a<=t;a++)for(let r=-t;r<=t;r++)for(let l=-t;l<=t;l++){const t=e+a,c=s+r,h=o+l;if(t<0||t>255||c<0||c>255||h<0||h>255)continue;const m=(255<<24|h<<16|c<<8|t)>>>0;i.has(m)||i.set(m,n.id)}}return{palette:e,N:i}}(this.qt),this.Vt=null,this.Le="",this.Rt=[],this.Ft=null,this.Ne=!0,this.Oe=null,this.Xt=new Map}async Be(){return{whoami:this.name.replace(" ",""),scriptVersion:this.version,schemaVersion:this.schemaVersion,templates:{}}}async Gt(t,e,n){this.Ft||(this.Ft=await this.Be()),this.I.Nt(`Creating template at ${n.join(", ")}...`);const s=new C({displayName:e,p:0,v:m(this.De||0,this.zt),file:t,coords:n}),{k:o,D:a}=await s.S(this.$,this.Jt);s.M=o;const r={total:s.T.total,colors:Object.fromEntries(s.T.colors)};this.Ft.templates[`${s.p} ${s.v}`]={name:s.displayName,coords:n.join(", "),enabled:!0,pixels:r,tiles:a},this.Rt=[],this.Rt.push(s),this.I.Nt(`Template created at ${n.join(", ")}!`),await i(this,N,O).call(this)}Ie(){}async He(){this.Ft||(this.Ft=await this.Be())}async Ae(){l("Downloading all templates...");for(const t of this.Rt)await this.We(t),await n(500)}async _t(){const t=JSON.parse(GM_getValue("bmTemplates","{}"))?.templates;if(Object.keys(t).length>0)for(const[e,i]of Object.entries(t))t.hasOwnProperty(e)&&(await this.We(new C({displayName:i.name,p:e.split(" ")?.[0],v:e.split(" ")?.[1],M:i.tiles})),await n(500))}async We(t){t.L();const e=`${t.coords.join("-")}_${t.displayName.replaceAll(" ","-")}`,i=await this.Ut(t);await GM.download({url:URL.createObjectURL(i),name:e+".png",Pe:"uniquify",onload:()=>{l(`Download of template '${e}' complete!`)},onerror:(t,i)=>{c(`Download of template '${e}' failed because ${t}! Details: ${i}`)},ontimeout:()=>{h(`Download of template '${e}' has timed out!`)}})}async Ut(t){const e=t.M,i=Object.keys(e).sort(),n=await Promise.all(i.map(t=>{return i=e[t],new Promise((t,e)=>{const n=new Image;n.onload=()=>t(n),n.onerror=e,n.src="data:image/png;base64,"+i});var i}));let s=1/0,o=1/0,a=0,r=0;i.forEach((t,e)=>{const[i,l,c,h]=t.split(",").map(Number),m=n[e],d=i*this.$+c,u=l*this.$+h;s=Math.min(s,d),o=Math.min(o,u),a=Math.max(a,d+m.width/this.jt),r=Math.max(r,u+m.height/this.jt)});const l=a-s,c=r-o,h=l*this.jt,m=c*this.jt,d=new OffscreenCanvas(h,m),u=d.getContext("2d");i.forEach((t,e)=>{const[i,a,r,l]=t.split(",").map(Number),c=n[e],h=i*this.$+r,m=a*this.$+l;u.drawImage(c,(h-s)*this.jt,(m-o)*this.jt,c.width,c.height)}),u.globalCompositeOperation="destination-over",u.drawImage(d,0,-1),u.drawImage(d,0,1),u.drawImage(d,-1,0),u.drawImage(d,1,0);const b=new OffscreenCanvas(l,c),p=b.getContext("2d");return p.imageSmoothingEnabled=!1,p.drawImage(d,0,0,l*this.jt,c*this.jt,0,0,l,c),b.convertToBlob({type:"image/png"})}async _e(t,e){if(!this.Ne)return t;const n=this.$*this.jt;e=e[0].toString().padStart(4,"0")+","+e[1].toString().padStart(4,"0");const o=this.Rt;o.sort((t,e)=>t.p-e.p);const a=o.map(t=>{const i=Object.keys(t.M).filter(t=>t.startsWith(e));if(0===i.length)return null;const n=i.map(e=>{const i=e.split(",");return{ze:t,Ue:t.M[e],C:t.C?.[e],Ge:[i[0],i[1]],Fe:[i[2],i[3]]}});return n?.[0]}).filter(Boolean),r=a?.length||0;if(!(r>0))return this.I.Nt(`Sleeping\nVersion: ${this.version}`),t;{const t=s(o.filter(t=>Object.keys(t.M).filter(t=>t.startsWith(e)).length>0).reduce((t,e)=>t+(e.T.total||0),0));this.I.Nt(`Displaying ${r} template${1==r?"":"s"}.\nTotal pixels: ${t}`)}const l=await createImageBitmap(t),c=new OffscreenCanvas(n,n),h=c.getContext("2d");h.imageSmoothingEnabled=!1,h.beginPath(),h.rect(0,0,n,n),h.clip(),h.clearRect(0,0,n,n),h.drawImage(l,0,0,n,n);const m=h.getImageData(0,0,n,n),d=new Uint32Array(m.data.buffer);for(const t of a){const n=!!t.ze.T?.colors?.get(-1);let s=t.C.slice();const o=Number(t.Fe[0])*this.jt,a=Number(t.Fe[1])*this.jt;if(0!=this.Xt.size||n||h.drawImage(t.Ue,o,a),!s){const e=h.getImageData(o,a,t.Ue.width,t.Ue.height);s=new Uint32Array(e.data.buffer)}Date.now();const{Qt:r,Zt:l}=i(this,N,I).call(this,{Et:d,Vt:s,Yt:[o,a,t.Ue.width,t.Ue.height]});let c=0;const m=0;for(const[t,e]of r)t!=m&&(c+=e);(0!=this.Xt.size||n)&&h.drawImage(await createImageBitmap(new ImageData(new Uint8ClampedArray(l.buffer),t.Ue.width,t.Ue.height)),o,a),void 0===t.ze.T.correct&&(t.ze.T.correct={}),t.ze.T.correct[e]=r}return await c.convertToBlob({type:"image/png"})}Re(t){"BlueMarble"==t?.whoami&&i(this,N,B).call(this,t)}ke(t){this.Ne=t}}(J,X,tt),it=new class{constructor(t){this.Wt=t,this.je=!1,this.Te="",this.Se=[],this.Ee=[]}Ve(t){window.addEventListener("message",async e=>{const i=e.data,n=i.jsonData;if(!i||"blue-marble"!==i.source)return;if(!i.endpoint)return;const o=i.endpoint?.split("?")[0].split("/").filter(t=>t&&isNaN(Number(t))).filter(t=>t&&!t.includes(".")).pop();switch(o){case"me":if(n.status&&"2"!=n.status?.toString()[0])return void t.Lt("You are not logged in or Wplace is offline!\nCould not fetch userdata.");const e=Math.ceil(Math.pow(Math.floor(n.level)*Math.pow(30,.65),1/.65)-n.pixelsPainted);if(n.id||n.id,this.Wt.De=n.id,0!=this.Te.length){const t=document.querySelector("#"+this.Te);if(t){const e=n.charges;t.dataset.endDate=Date.now()+(e.max-e.count)*e.cooldownMs}}t.vt("bm-q",`Droplets: ${s(n.droplets)}`),t.vt("bm-k",`Next level in ${s(e)} pixel${1==e?"":"s"}`);break;case"pixel":const o=i.endpoint.split("?")[0].split("/").filter(t=>t&&!isNaN(Number(t))),l=new URLSearchParams(i.endpoint.split("?")[1]),c=[l.get("x"),l.get("y")];if(this.Se.length&&(!o.length||!c.length))return void t.Lt("Coordinates are malformed!\nDid you try clicking the canvas first?");this.Se=[...o,...c];const h=(a=o,r=c,[parseInt(a[0])%4*1e3+parseInt(r[0]),parseInt(a[1])%4*1e3+parseInt(r[1])]),m=document.querySelectorAll("span");for(const t of m)if(t.textContent.trim().includes(`${h[0]}, ${h[1]}`)){let e=document.querySelector("#bm-j");const i=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${c[0]}, Px Y: ${c[1]})`;e?e.textContent=i:(e=document.createElement("span"),e.id="bm-j",e.textContent=i,e.style="margin-left: calc(var(--spacing)*3); font-size: small;",t.parentNode.parentNode.insertAdjacentElement("afterend",e))}break;case"tile":case"tiles":let d=i.endpoint.split("/");d=[parseInt(d[d.length-2]),parseInt(d[d.length-1].replace(".png",""))];const u=i.blobID,b=i.blobData,p=(Date.now(),await this.Wt._e(b,d));window.postMessage({source:"blue-marble",blobID:u,blobData:p,blink:i.blink});break;case"robots":this.je="false"==n.userscript?.toString().toLowerCase()}var a,r})}async Ye(t){let e=GM_getValue("bmUserSettings","{}");if(e=JSON.parse(e),!e||!e.telemetry||!e.uuid)return;const i=navigator.userAgent;let n=await this.qe(i),s=this.Je(i);GM_xmlhttpRequest({method:"POST",url:"https://telemetry.thebluecorner.net/heartbeat",headers:{"Content-Type":"application/json"},data:JSON.stringify({uuid:e.uuid,version:t,browser:n,os:s}),onload:t=>{200!==t.status&&c("Failed to send heartbeat:",t.statusText)},onerror:t=>{c("Error sending heartbeat:",t)}})}async qe(t=navigator.userAgent){return(t=t||"").includes("OPR/")||t.includes("Opera")?"Opera":t.includes("Edg/")?"Edge":t.includes("Vivaldi")?"Vivaldi":t.includes("YaBrowser")?"Yandex":t.includes("Kiwi")?"Kiwi":t.includes("Brave")?"Brave":t.includes("Firefox/")?"Firefox":t.includes("Chrome/")?"Chrome":t.includes("Safari/")?"Safari":navigator.brave&&"function"==typeof navigator.brave.isBrave&&await navigator.brave.isBrave()?"Brave":"Unknown"}Je(t=navigator.userAgent){return/Windows NT 11/i.test(t=t||"")?"Windows 11":/Windows NT 10/i.test(t)?"Windows 10":/Windows NT 6\.3/i.test(t)?"Windows 8.1":/Windows NT 6\.2/i.test(t)?"Windows 8":/Windows NT 6\.1/i.test(t)?"Windows 7":/Windows NT 6\.0/i.test(t)?"Windows Vista":/Windows NT 5\.1|Windows XP/i.test(t)?"Windows XP":/Mac OS X 10[_\.]15/i.test(t)?"macOS Catalina":/Mac OS X 10[_\.]14/i.test(t)?"macOS Mojave":/Mac OS X 10[_\.]13/i.test(t)?"macOS High Sierra":/Mac OS X 10[_\.]12/i.test(t)?"macOS Sierra":/Mac OS X 10[_\.]11/i.test(t)?"OS X El Capitan":/Mac OS X 10[_\.]10/i.test(t)?"OS X Yosemite":/Mac OS X 10[_\.]/i.test(t)?"macOS":/Android/i.test(t)?"Android":/iPhone|iPad|iPod/i.test(t)?"iOS":/Linux/i.test(t)?"Linux":"Unknown"}}(et);tt.W(it);var nt=JSON.parse(GM_getValue("bmTemplates","{}"));et.Re(nt);var st=JSON.parse(GM_getValue("bmUserSettings","{}"));if(0==Object.keys(st).length){const t=crypto.randomUUID();GM.setValue("bmUserSettings",JSON.stringify({uuid:t}))}setInterval(()=>it.Ye(X),18e5);var ot=st?.telemetry;if(null==ot||ot>1){const t=new class extends D{constructor(t,i,n,s){super(t,i),e(this,E),this.window=null,this.Ot="bm-g",this.Bt=document.body,this.Xe=n,this.uuid=s}async Pt(){if(document.querySelector(`#${this.Ot}`))return void this.Lt("Telemetry window already exists!");const t=await this.O.qe(navigator.userAgent),e=this.O.Je(navigator.userAgent);this.window=this.U({id:this.Ot,class:"bm-N",style:"height: 80vh; z-index: 9998;"}).U({class:"bm-h"}).U({class:"bm-E bm-d"}).q(1,{textContent:`${this.name} Telemetry`}).P().P().J().P().U({class:"bm-E bm-x",style:"gap: 1.5ch; flex-wrap: wrap;"}).yt({textContent:"Enable Telemetry"},(t,e)=>{e.onclick=()=>{i(this,E,V).call(this,this.Xe);const t=document.getElementById(this.Ot);t?.remove()}}).P().yt({textContent:"Disable Telemetry"},(t,e)=>{e.onclick=()=>{i(this,E,V).call(this,0);const t=document.getElementById(this.Ot);t?.remove()}}).P().yt({textContent:"More Information"},(t,e)=>{e.onclick=()=>{window.open("https://github.com/SwingTheVine/Wplace-TelemetryServer#telemetry-data","_blank","noopener noreferrer")}}).P().P().U({class:"bm-E bm-A"}).U({class:"bm-E"}).q(2,{textContent:"Legal"}).P().G({textContent:`We collect anonymous telemetry data such as your browser, OS, and script version to make the experience better for everyone. The data is never shared personally. The data is never sold. You can turn this off by pressing the "Disable" button, but keeping it on helps us improve features and reliability faster. Thank you for supporting ${this.name}!`}).P().P().J().P().U({class:"bm-E"}).q(2,{textContent:"Non-Legal Summary"}).P().G({innerHTML:'You can disable telemetry by pressing the "Disable" button. If you would like to read more about what information we collect, press the "More Information" button.
This is the data stored on our servers:'}).P().ot().lt({innerHTML:`A unique identifier (UUIDv4) generated by Blue Marble. This enables our telemetry to function without tracking your actual user ID.
Your UUID is: ${r(this.uuid)}`}).P().lt({innerHTML:`The version of Blue Marble you are using.
Your version is: ${r(this.version)}`}).P().lt({innerHTML:`Your browser type, which is used to determine Blue Marble outages and browser popularity.
Your browser type is: ${r(t)}`}).P().lt({innerHTML:`Your OS type, which is used to determine Blue Marble outages and OS popularity.
Your OS type is: ${r(e)}`}).P().lt({innerHTML:"The date and time that Blue Marble sent the telemetry information."}).P().P().G({innerHTML:'All of the data mentioned above is aggregated every hour. This means every hour, anything that could even remotly be considered "personal data" is deleted from our server. Here, "aggregated" data means things like "42 people used Blue Marble on Google Chrome this hour", which can\'t be used to identify anyone in particular.'}).P().P().P().P().P()._(this.Bt)}}(J,X,1,st?.uuid);t.W(it),t.Pt()}tt.Pt(),it.Ve(tt),new MutationObserver((t,e)=>{const i=document.querySelector("#color-1");if(!i)return;let n=document.querySelector("#bm-z");if(!n){n=document.createElement("button"),n.id="bm-z",n.textContent="Move ↑",n.className="btn btn-soft",n.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 ↑"};const t=i.parentNode.parentNode.parentNode.parentNode.querySelector("h2");t.parentNode?.appendChild(n)}}).observe(document.body,{childList:!0,subtree:!0}),l(`%c${J}%c (${X}) userscript has loaded!`,"color: cornflowerblue;","")})(); \ No newline at end of file +(()=>{var t=t=>{throw TypeError(t)},e=(e,i,n)=>i.has(e)?t("Cannot add the same private member more than once"):i instanceof WeakSet?i.add(e):i.set(e,n),i=(e,i,n)=>(((e,i)=>{i.has(e)||t("Cannot access private method")})(e,i),n);function n(t){return new Promise(e=>setTimeout(e,t))}function s(t){return(new Intl.NumberFormat).format(t)}function o(t){return new Intl.NumberFormat(void 0,{style:"percent",t:2,i:2}).format(t)}function a(t){return t.toLocaleString(void 0,{o:"long",l:"numeric",h:"2-digit",m:"2-digit",u:"2-digit"})}function r(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}function l(...t){(0,console.log)(...t)}function c(...t){(0,console.error)(...t)}function h(...t){(0,console.warn)(...t)}function m(t,e){if(0===t)return e[0];let i="";const n=e.length;for(;t>0;)i=e[t%n]+i,t=Math.floor(t/n);return i}function d(t,e){let i=0;const n=e.length;for(const s of t){const t=e.indexOf(s);-1==t&&c(`Invalid character '${s}' encountered whilst decoding! Is the decode alphabet/base incorrect?`),i=i*n+t}return i}function u(t){let e="";for(let i=0;i(t/=255)<=.03928?t/12.92:Math.pow((t+.055)/1.055,2.4));return.2126*e[0]+.7152*e[1]+.0722*e[2]}function g(t,e,i){return Array.isArray(t)&&([t,e,i]=t),(1<<24|t<<16|e<<8|i).toString(16).slice(1)}var f,w,x,y,$,v=[{id:0,premium:!1,name:"Transparent",rgb:[0,0,0]},{id:1,premium:!1,name:"Black",rgb:[0,0,0]},{id:2,premium:!1,name:"Dark Gray",rgb:[60,60,60]},{id:3,premium:!1,name:"Gray",rgb:[120,120,120]},{id:4,premium:!1,name:"Light Gray",rgb:[210,210,210]},{id:5,premium:!1,name:"White",rgb:[255,255,255]},{id:6,premium:!1,name:"Deep Red",rgb:[96,0,24]},{id:7,premium:!1,name:"Red",rgb:[237,28,36]},{id:8,premium:!1,name:"Orange",rgb:[255,127,39]},{id:9,premium:!1,name:"Gold",rgb:[246,170,9]},{id:10,premium:!1,name:"Yellow",rgb:[249,221,59]},{id:11,premium:!1,name:"Light Yellow",rgb:[255,250,188]},{id:12,premium:!1,name:"Dark Green",rgb:[14,185,104]},{id:13,premium:!1,name:"Green",rgb:[19,230,123]},{id:14,premium:!1,name:"Light Green",rgb:[135,255,94]},{id:15,premium:!1,name:"Dark Teal",rgb:[12,129,110]},{id:16,premium:!1,name:"Teal",rgb:[16,174,166]},{id:17,premium:!1,name:"Light Teal",rgb:[19,225,190]},{id:18,premium:!1,name:"Dark Blue",rgb:[40,80,158]},{id:19,premium:!1,name:"Blue",rgb:[64,147,228]},{id:20,premium:!1,name:"Cyan",rgb:[96,247,242]},{id:21,premium:!1,name:"Indigo",rgb:[107,80,246]},{id:22,premium:!1,name:"Light Indigo",rgb:[153,177,251]},{id:23,premium:!1,name:"Dark Purple",rgb:[120,12,153]},{id:24,premium:!1,name:"Purple",rgb:[170,56,185]},{id:25,premium:!1,name:"Light Purple",rgb:[224,159,249]},{id:26,premium:!1,name:"Dark Pink",rgb:[203,0,122]},{id:27,premium:!1,name:"Pink",rgb:[236,31,128]},{id:28,premium:!1,name:"Light Pink",rgb:[243,141,169]},{id:29,premium:!1,name:"Dark Brown",rgb:[104,70,52]},{id:30,premium:!1,name:"Brown",rgb:[149,104,42]},{id:31,premium:!1,name:"Beige",rgb:[248,178,119]},{id:32,premium:!0,name:"Medium Gray",rgb:[170,170,170]},{id:33,premium:!0,name:"Dark Red",rgb:[165,14,30]},{id:34,premium:!0,name:"Light Red",rgb:[250,128,114]},{id:35,premium:!0,name:"Dark Orange",rgb:[228,92,26]},{id:36,premium:!0,name:"Light Tan",rgb:[214,181,148]},{id:37,premium:!0,name:"Dark Goldenrod",rgb:[156,132,49]},{id:38,premium:!0,name:"Goldenrod",rgb:[197,173,49]},{id:39,premium:!0,name:"Light Goldenrod",rgb:[232,212,95]},{id:40,premium:!0,name:"Dark Olive",rgb:[74,107,58]},{id:41,premium:!0,name:"Olive",rgb:[90,148,74]},{id:42,premium:!0,name:"Light Olive",rgb:[132,197,115]},{id:43,premium:!0,name:"Dark Cyan",rgb:[15,121,159]},{id:44,premium:!0,name:"Light Cyan",rgb:[187,250,242]},{id:45,premium:!0,name:"Light Blue",rgb:[125,199,255]},{id:46,premium:!0,name:"Dark Indigo",rgb:[77,49,184]},{id:47,premium:!0,name:"Dark Slate Blue",rgb:[74,66,132]},{id:48,premium:!0,name:"Slate Blue",rgb:[122,113,196]},{id:49,premium:!0,name:"Light Slate Blue",rgb:[181,174,241]},{id:50,premium:!0,name:"Light Brown",rgb:[219,164,99]},{id:51,premium:!0,name:"Dark Beige",rgb:[209,128,81]},{id:52,premium:!0,name:"Light Beige",rgb:[255,197,165]},{id:53,premium:!0,name:"Dark Peach",rgb:[155,82,73]},{id:54,premium:!0,name:"Peach",rgb:[209,128,120]},{id:55,premium:!0,name:"Light Peach",rgb:[250,182,164]},{id:56,premium:!0,name:"Dark Tan",rgb:[123,99,82]},{id:57,premium:!0,name:"Tan",rgb:[156,132,107]},{id:58,premium:!0,name:"Dark Slate",rgb:[51,57,65]},{id:59,premium:!0,name:"Slate",rgb:[109,117,141]},{id:60,premium:!0,name:"Light Slate",rgb:[179,185,209]},{id:61,premium:!0,name:"Dark Stone",rgb:[109,100,63]},{id:62,premium:!0,name:"Stone",rgb:[148,140,107]},{id:63,premium:!0,name:"Light Stone",rgb:[205,197,158]}],M=class{constructor(t,i){e(this,f),this.name=t,this.version=i,this.p=null,this.$=null,this.v="bm-l",this.M=null,this.C=null,this.T=[]}S(t){this.p=t}k(t){this.$=t}D(){return this.T.length>0&&(this.C=this.T.pop()),this}L(t){t?.appendChild(this.M),this.M=null,this.C=null,this.T=[]}H(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"div",{},t)),this}O(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"p",{},t)),this}N(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"small",{},t)),this}B(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"span",{},t)),this}I(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"details",{},t)),this}P(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"summary",{},t)),this}A(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"img",{},t)),this}W(t,e={},n=()=>{}){return n(this,i(this,f,w).call(this,"h"+t,{},e)),this}F(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"hr",{},t)),this}V(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"br",{},t)),this}_(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"form",{},t)),this}U(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"fieldset",{},t)),this}G(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"legend",{},t)),this}R(t={},e=()=>{}){const n={};t.textContent?(n.textContent=t.textContent,delete t.textContent):t.innerHTML&&(n.innerHTML=t.innerHTML,delete t.textContent);const s=i(this,f,w).call(this,"label",n),o=i(this,f,w).call(this,"input",{type:"checkbox"},t);return s.insertBefore(o,s.firstChild),this.D(),e(this,s,o),this}j(t={},e=()=>{}){const n=i(this,f,w).call(this,"label",{textContent:t.textContent??"",for:t.id??""});return delete t.textContent,this.D(),e(this,n,i(this,f,w).call(this,"select",{},t)),this}Y(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"option",{},t)),this}J(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"ol",{},t)),this}X(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"ul",{},t)),this}q(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"menu",{},t)),this}Z(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"li",{},t)),this}K(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"table",{},t)),this}tt(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"caption",{},t)),this}et(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"thead",{},t)),this}it(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"tbody",{},t)),this}nt(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"tfoot",{},t)),this}st(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"tr",{},t)),this}ot(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"th",{},t)),this}rt(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"td",{},t)),this}lt(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"button",{},t)),this}ct(t={},e=()=>{}){const n=t.title??t.textContent??"Help: No info";delete t.textContent,t.title=`Help: ${n}`;const s={textContent:"?",className:"bm-R",onclick:()=>{this.ht(this.v,n)}};return e(this,i(this,f,w).call(this,"button",s,t)),this}dt(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"input",{},t)),this}ut(t={},e=()=>{}){const n=t.textContent??"";delete t.textContent;const s=i(this,f,w).call(this,"div"),o=i(this,f,w).call(this,"input",{type:"file",tabindex:"-1","aria-hidden":"true"},t);this.D();const a=i(this,f,w).call(this,"button",{textContent:n});return this.D(),this.D(),a.addEventListener("click",()=>{o.click()}),o.addEventListener("change",()=>{a.style.maxWidth=`${a.offsetWidth}px`,o.files.length>0?a.textContent=o.files[0].name:a.textContent=n}),e(this,s,o,a),this}bt(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"textarea",{},t)),this}gt(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"div",{class:"bm-L"},t)),this}ft(t=Date.now(),e=500,n={},s=()=>{}){const o="bm-P",a=n?.id||o+"-"+crypto.randomUUID().slice(0,8),r={class:o},l=i(this,f,w).call(this,"time",r,n);return l.id=a,l.dataset.endDate=t,setInterval(()=>{if(!l.isConnected)return;const t=Math.max(l.dataset.endDate-Date.now(),0),e=Math.floor(t/1e3),i=Math.floor(e/3600),n=Math.floor(e%60),s=Math.floor(e%3600/60);l.setAttribute("datetime",`PT${i}H${s}M${n}S`),l.textContent=String(i).padStart(2,"0")+":"+String(s).padStart(2,"0")+":"+String(n).padStart(2,"0")},e),s(this,l),this}ht(t,e,i=!1){const n=document.getElementById(t.replace(/^#/,""));n&&(n instanceof HTMLInputElement?n.value=e:i?n.textContent=e:n.innerHTML=e)}wt(t){if(t.disabled)return;t.disabled=!0,t.style.textDecoration="none";const e=t.closest(".bm-N"),i=t.closest(".bm-L"),n=e.querySelector("h1"),s=e.querySelector(".bm-h");if(e.parentElement.append(e),"expanded"==t.dataset.buttonStatus){s.style.height=s.scrollHeight+"px",e.style.width=e.scrollWidth+"px",s.style.height="0",s.addEventListener("transitionend",function e(){s.style.display="none",t.disabled=!1,t.style.textDecoration="",s.removeEventListener("transitionend",e)});const i=n.cloneNode(!0),o=i.textContent;t.nextElementSibling.appendChild(i),t.textContent="▶",t.dataset.buttonStatus="collapsed",t.ariaLabel=`Unminimize window "${o}"`}else{const n=i.querySelector("h1"),o=n.textContent;n.remove(),s.style.display="",s.style.height="0",e.style.width="",s.style.height=s.scrollHeight+"px",s.addEventListener("transitionend",function e(){s.style.height="",t.disabled=!1,t.style.textDecoration="",s.removeEventListener("transitionend",e)}),t.textContent="▼",t.dataset.buttonStatus="expanded",t.ariaLabel=`Minimize window "${o}"`}}xt(t,e){const i=document.querySelector(t),n=document.querySelector(e);if(!i||!n)return void this.yt(`Can not drag! ${i?"":"moveMe"} ${i||n?"":"and "}${n?"":"iMoveThings "}was not found!`);let s,o=!1,a=0,r=null,l=0,c=0,h=0,m=0,d=null;const u=()=>{if(o){const t=Math.abs(l-h),e=Math.abs(c-m);(t>.5||e>.5)&&(l=h,c=m,i.style.transform=`translate(${l}px, ${c}px)`,i.style.left="0px",i.style.top="0px",i.style.right=""),r=requestAnimationFrame(u)}},b=(t,e)=>{o=!0,d=i.getBoundingClientRect(),s=t-d.left,a=e-d.top;const b=window.getComputedStyle(i).transform;if(b&&"none"!==b){const t=new DOMMatrix(b);l=t.m41,c=t.m42}else l=d.left,c=d.top;h=l,m=c,document.body.style.userSelect="none",n.classList.add("bm-F"),document.addEventListener("mousemove",g),document.addEventListener("touchmove",f,{passive:!1}),document.addEventListener("mouseup",p),document.addEventListener("touchend",p),document.addEventListener("touchcancel",p),r&&cancelAnimationFrame(r),u()},p=()=>{o=!1,r&&(cancelAnimationFrame(r),r=null),document.body.style.userSelect="",n.classList.remove("bm-F"),document.removeEventListener("mousemove",g),document.removeEventListener("touchmove",f),document.removeEventListener("mouseup",p),document.removeEventListener("touchend",p),document.removeEventListener("touchcancel",p)},g=t=>{o&&d&&(h=t.clientX-s,m=t.clientY-a)},f=t=>{if(o&&d){const e=t.touches[0];if(!e)return;h=e.clientX-s,m=e.clientY-a,t.preventDefault()}};n.addEventListener("mousedown",function(t){t.preventDefault(),b(t.clientX,t.clientY)}),n.addEventListener("touchstart",function(t){const e=t?.touches?.[0];e&&(b(e.clientX,e.clientY),t.preventDefault())},{passive:!1})}$t(t){(0,console.info)(`${this.name}: ${t}`),this.ht(this.v,"Status: "+t,!0)}yt(t){(0,console.error)(`${this.name}: ${t}`),this.ht(this.v,"Error: "+t,!0)}};f=new WeakSet,w=function(t,e={},n={}){const s=document.createElement(t);this.M?(this.C?.appendChild(s),this.T.push(this.C),this.C=s):(this.M=s,this.C=s);for(const[t,n]of Object.entries(e))i(this,f,x).call(this,s,t,n);for(const[t,e]of Object.entries(n))i(this,f,x).call(this,s,t,e);return s},x=function(t,e,i){"class"==e?t.classList.add(...i.split(/\s+/)):"for"==e?t.htmlFor=i:"tabindex"==e?t.tabIndex=Number(i):"readonly"==e?t.readOnly="true"==i||"1"==i:"maxlength"==e?t.maxLength=Number(i):e.startsWith("data")?t.dataset[e.slice(5).split("-").map((t,e)=>0==e?t:t[0].toUpperCase()+t.slice(1)).join("")]=i:e.startsWith("aria")?t.setAttribute(e,i):t[e]=i};var C,T,S,k,D,L=class extends M{constructor(t,i){super(t,i),e(this,y),this.window=null,this.vt="bm-N-settings",this.Mt=document.body}Ct(){document.querySelector(`#${this.vt}`)?document.querySelector(`#${this.vt}`).remove():(this.window=this.H({id:this.vt,class:"bm-N"}).gt().lt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Color Filter"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.wt(e),e.ontouchend=()=>{e.click()}}).D().H().D().H({class:"bm-x"}).lt({class:"bm-n",textContent:"✖","aria-label":'Close window "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.vt}`)?.remove()},e.ontouchend=()=>{e.click()}}).D().D().D().H({class:"bm-h"}).H({class:"bm-E bm-d"}).W(1,{textContent:"Settings"}).D().D().F().D().O({textContent:"Settings take 5 seconds to save."}).D().H({class:"bm-E bm-A"},(t,e)=>{this.Tt(),this.St()}).D().D().D().L(this.Mt),this.xt(`#${this.vt}.bm-N`,`#${this.vt} .bm-L`))}Tt(){i(this,y,$).call(this,"Pixel Highlight")}St(){i(this,y,$).call(this,"Template")}};y=new WeakSet,$=function(t){this.window=this.H({class:"bm-E"}).W(2,{textContent:t}).D().F().D().O({innerHTML:`An error occured loading the ${t} category. SettingsManager failed to override the ${t} function inside WindowSettings.`}).D().D()},C=new WeakSet,T=function(t,e){t.disabled=!0;const i=t.dataset.status,n=this.kt?.highlight??[[1,0,1],[2,0,0],[1,-1,0],[1,1,0],[1,0,-1]];let s=[2,0,0];const o=n;switch(i){case"Disabled":t.dataset.status="Incorrect",t.ariaLabel="Sub-pixel incorrect",s=[1,...e];break;case"Incorrect":t.dataset.status="Template",t.ariaLabel="Sub-pixel template",s=[2,...e];break;case"Template":t.dataset.status="Disabled",t.ariaLabel="Sub-pixel disabled",s=[0,...e];break}const a=n.findIndex(([,t,e])=>t==s[1]&&e==s[2]);0!=s[0]?-1!=a?o[a]=s:o.push(s):-1!=a&&o.splice(a,1),this.kt.highlight=o,t.disabled=!1},S=async function(t){const e=document.querySelectorAll(".bm-Y button");for(const t of e)t.disabled=!0;let i=[0,0,0,0,2,0,0,0,0];switch(t){case"Cross":i=[0,1,0,1,2,1,0,1,0];break;case"X":i=[1,0,1,0,2,0,1,0,1];break;case"Full":i=[2,2,2,2,2,2,2,2,2];break}const s=document.querySelector(".bm-1g")?.childNodes??[];for(let t=0;t{const[n,s,o,a]=e.split(",").map(Number);(s>>24==0?0:s.get(e)??-2;const a=o.get(n);o.set(n,a?a+1:1)}return console.log(o),o};var O=class{constructor(){this.Et=Math.ceil(80/1300*window.innerWidth),this.Yt=v.slice(1)}Jt(t){const e=document.createElement("div");for(let t=0;t{t.parentNode.childElementCount<=1?t.parentNode.remove():t.remove()},e.appendChild(t)}t.appendChild(e)}},N=class extends HTMLElement{};customElements.define("confetti-piece",N);var B,I,P,A,W,F,V,z,_,U=class extends M{constructor(t,e){super(t,e),this.window=null,this.vt="bm-i",this.Mt=document.body}Ct(){document.querySelector(`#${this.vt}`)?document.querySelector(`#${this.vt}`).remove():(this.window=this.H({id:this.vt,class:"bm-N"},(t,e)=>{}).gt().lt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Credits"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.wt(e),e.ontouchend=()=>{e.click()}}).D().H().D().lt({class:"bm-n",textContent:"✖","aria-label":'Close window "Credits"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.vt}`)?.remove()},e.ontouchend=()=>{e.click()}}).D().D().H({class:"bm-h"}).H({class:"bm-E bm-d"}).W(1,{textContent:"Credits"}).D().D().F().D().H({class:"bm-E bm-A"}).B({role:"img","aria-label":this.name}).B({innerHTML:"\n██████╗ ██╗ ██╗ ██╗███████╗\n██╔══██╗██║ ██║ ██║██╔════╝\n██████╔╝██║ ██║ ██║█████╗ \n██╔══██╗██║ ██║ ██║██╔══╝ \n██████╔╝███████╗╚██████╔╝███████╗\n╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝\n\n███╗ ███╗ █████╗ ██████╗ ██████╗ ██╗ ███████╗\n████╗ ████║██╔══██╗██╔══██╗██╔══██╗██║ ██╔════╝\n██╔████╔██║███████║██████╔╝██████╔╝██║ █████╗ \n██║╚██╔╝██║██╔══██║██╔══██╗██╔══██╗██║ ██╔══╝ \n██║ ╚═╝ ██║██║ ██║██║ ██║██████╔╝███████╗███████╗\n╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚══════╝\n",class:"bm-Q","aria-hidden":"true"}).D().D().V().D().F().D().V().D().B({textContent:'"Blue Marble" userscript is made by SwingTheVine.'}).D().V().D().B({innerHTML:'The Blue Marble Website is made by crqch.'}).D().V().D().B({textContent:`The Blue Marble Website used until ${a(new Date(175606932e4))} was made by Camille Daguin.`}).D().V().D().B({textContent:'The favicon "Blue Marble" is owned by NASA. (The image of the Earth is owned by NASA)'}).D().V().D().B({textContent:"Special Thanks:"}).D().X().Z({textContent:"Espresso, Meqa, and Robot for moderating SwingTheVine's community."}).D().Z({innerHTML:'nof, darkness for creating similar userscripts!'}).D().Z({innerHTML:'Wonda for the Blue Marble banner image!'}).D().Z({innerHTML:'BullStein, allanf181 for being early beta testers!'}).D().Z({innerHTML:'guidu_ and Nick-machado for the original "Minimize" Button code!'}).D().Z({innerHTML:'Nomad and Gustav for the tutorials!'}).D().Z({innerHTML:'cfp for creating the template overlay that Blue Marble was based on!'}).D().Z({innerHTML:'Force Network for hosting the telemetry server!'}).D().Z({innerHTML:'TheBlueCorner for getting me interested in online pixel canvases!'}).D().D().V().D().B({innerHTML:'Donators:'}).D().X().Z({textContent:"Espresso"}).D().Z({textContent:"BEST FAN"}).D().Z({textContent:"FuchsDresden"}).D().Z({textContent:"Jack"}).D().Z({textContent:"raiken_au"}).D().Z({textContent:"Jacob"}).D().Z({textContent:"StupidOne"}).D().Z({textContent:"2 Anonymous Supporters"}).D().D().D().D().D().L(this.Mt),this.xt(`#${this.vt}.bm-N`,`#${this.vt} .bm-L`))}},G=class extends M{constructor(t){super(t.name,t.version),e(this,B),this.window=null,this.vt="bm-p",this.Xt="bm-y",this.Mt=document.body,this.qt=t.p?.qt,this.Zt='',this.Qt='';const{palette:i,jt:n}=this.qt.Kt;this.palette=i,this.te=0,this.ee=0,this.ie=new Map,this.ne=new Map,this.se=0,this.oe=0,this.timeRemaining=0,this.ae="",this.sortPrimary="id",this.sortSecondary="ascending",this.showUnused=!1}Ct(){if(document.querySelector(`#${this.vt}`))return void document.querySelector(`#${this.vt}`).remove();this.window=this.H({id:this.vt,class:"bm-N"},(t,e)=>{}).gt().lt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Color Filter"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.wt(e),e.ontouchend=()=>{e.click()}}).D().H().D().H({class:"bm-x"}).lt({class:"bm-n",textContent:"🗗","aria-label":'Switch to windowed mode for "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.vt}`)?.remove(),this.re()},e.ontouchend=()=>{e.click()}}).D().lt({class:"bm-n",textContent:"✖","aria-label":'Close window "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.vt}`)?.remove()},e.ontouchend=()=>{e.click()}}).D().D().D().H({class:"bm-h"}).H({class:"bm-E bm-d"}).W(1,{textContent:"Color Filter"}).D().D().F().D().H({class:"bm-E bm-s bm-d",style:"gap: 1.5ch;"}).lt({textContent:"Hide All Colors"},(t,e)=>{e.onclick=()=>i(this,B,A).call(this,!1)}).D().lt({textContent:"Refresh Data"},(t,e)=>{e.onclick=()=>{e.disabled=!0,this.le(),e.disabled=!1}}).D().lt({textContent:"Show All Colors"},(t,e)=>{e.onclick=()=>i(this,B,A).call(this,!0)}).D().D().H({class:"bm-E bm-A"}).H({class:"bm-E",style:"margin-left: 2.5ch; margin-right: 2.5ch;"}).H({class:"bm-E"}).B({id:"bm-e",innerHTML:"Tiles Loaded: 0 / ???"}).D().V().D().B({id:"bm-9",innerHTML:"Correct Pixels: ???"}).D().V().D().B({id:"bm-f",innerHTML:"Total Pixels: ???"}).D().V().D().B({id:"bm-4",innerHTML:"Complete: ??? (???)"}).D().V().D().B({id:"bm-5",innerHTML:"??? ???"}).D().D().H({class:"bm-E"}).O({innerHTML:`Press the 🗗 button to make this window smaller. Colors with the icon ${this.Zt.replace("{e.onclick=t=>{t.preventDefault();const e=new FormData(document.querySelector(`#${this.vt} form`)),n={};for(const[t,i]of e)n[t]=i;console.log(`Primary: ${n.sortPrimary}; Secondary: ${n.sortSecondary}; Unused: ${"on"==n.showUnused}`),i(this,B,P).call(this,n.sortPrimary,n.sortSecondary,"on"==n.showUnused)}}).D().D().D().D().D().D().D().L(this.Mt),this.xt(`#${this.vt}.bm-N`,`#${this.vt} .bm-L`);const t=document.querySelector(`#${this.vt} .bm-E.bm-A`);i(this,B,I).call(this,t),i(this,B,P).call(this,this.sortPrimary,this.sortSecondary,this.showUnused),this.ht("#bm-e",`Tiles Loaded: ${s(this.te)} / ${s(this.ee)}`),this.ht("#bm-9",`Correct Pixels: ${s(this.se)}`),this.ht("#bm-f",`Total Pixels: ${s(this.oe)}`),this.ht("#bm-4",`Remaining: ${s((this.oe||0)-(this.se||0))} (${o(((this.oe||0)-(this.se||0))/(this.oe||1))})`),this.ht("#bm-5",`Completed at: `)}re(){if(document.querySelector(`#${this.vt}`))return void document.querySelector(`#${this.vt}`).remove();this.window=this.H({id:this.vt,class:"bm-N bm-G"}).gt().lt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Color Filter"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>{const i=document.querySelector("#bm-X");i&&(i.style.display="expanded"==e.dataset.buttonStatus?"none":""),t.wt(e)},e.ontouchend=()=>{e.click()}}).D().H().B({id:"bm-X",class:"bm-L-text",style:"font-weight: 700;"}).D().D().H({class:"bm-x"}).lt({class:"bm-n",textContent:"🗖","aria-label":'Switch to fullscreen mode for "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.vt}`)?.remove(),this.Ct()},e.ontouchend=()=>{e.click()}}).D().lt({class:"bm-n",textContent:"✖","aria-label":'Close window "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.vt}`)?.remove()},e.ontouchend=()=>{e.click()}}).D().D().D().H({class:"bm-h"}).H({class:"bm-E bm-d"}).W(1,{textContent:"Color Filter"}).D().D().F().D().H({class:"bm-E bm-s bm-d",style:"gap: 1.5ch;"}).lt({textContent:"None"},(t,e)=>{e.onclick=()=>i(this,B,A).call(this,!1)}).D().lt({textContent:"Refresh"},(t,e)=>{e.onclick=()=>{e.disabled=!0,this.le(),e.disabled=!1}}).D().lt({textContent:"All"},(t,e)=>{e.onclick=()=>i(this,B,A).call(this,!0)}).D().D().H({class:"bm-E bm-A"}).D().D().D().L(this.Mt),this.xt(`#${this.vt}.bm-N`,`#${this.vt} .bm-L`);const t=document.querySelector(`#${this.vt} .bm-E.bm-A`);i(this,B,I).call(this,t),i(this,B,P).call(this,this.sortPrimary,this.sortSecondary,this.showUnused)}le(){i(this,B,W).call(this);const t=document.querySelector(`#${this.Xt}`),e={};for(const t of this.palette){const i=this.ie.get(t.id)??0,n=s(i);let a=0,r="0",l=o(1);0!=i&&(a=this.ne.get(t.id)??"???","number"!=typeof a&&this.te==this.ee&&t.id&&(a=0),r="string"==typeof a?a:s(a),l=isNaN(a/i)?"???":o(a/i));const c=parseInt(i)-parseInt(a);e[t.id]={ce:i,he:n,me:a,de:r,ue:l,be:c}}if(document.querySelector("#bm-X")){const t=this.se.toString().length>7?this.se.toString().slice(0,2)+"…"+this.se.toString().slice(-3):this.se.toString(),e=this.oe.toString().length>7?this.oe.toString().slice(0,2)+"…"+this.oe.toString().slice(-3):this.oe.toString();this.ht("#bm-X",`${t}/${e}`,!0)}if(!t)return e;const n=Array.from(t.children);for(const t of n){const i=parseInt(t.dataset.id),{me:n,de:s,ue:o,ce:a,he:r,be:l}=e[i];t.dataset.correct=Number.isNaN(parseInt(n))?"0":n,t.dataset.total=a,t.dataset.percent="%"==o.slice(-1)?o.slice(0,-1):"0",t.dataset.incorrect=l||0;const c=document.querySelector(`#${this.vt} .bm-t[data-id="${i}"] .bm-6`);c&&(c.textContent=`${s} / ${r}`);const h=document.querySelector(`#${this.vt} .bm-t[data-id="${i}"] .bm-3`);h&&(h.textContent=`${"number"!=typeof l||isNaN(l)?"???":l} incorrect pixel${1==l?"":"s"}. Completed: ${o}`)}i(this,B,P).call(this,this.sortPrimary,this.sortSecondary,this.showUnused)}};B=new WeakSet,I=function(t){const e=t.closest(`#${this.vt}`)?.classList.contains("bm-G");console.log(`Is Windowed Mode: ${e}`);const i=new M(this.name,this.version);i.H({id:this.Xt});const n=this.le();for(const t of this.palette){const s="#"+g(t.rgb).toUpperCase(),o=p(t.rgb);let a=1.05/(o+.05)>(o+.05)/.05?"white":"black";t.id||(a="transparent");const r="white"==a?"bm-b":"bm-c",{me:l,de:c,ue:h,ce:m,he:d,be:u}=n[t.id],b=!!this.qt.pe.get(t.id);if(e){const e=`background-size: auto 100%; background-repeat: repeat-x; background-image: url("data:image/svg+xml;utf8,");`;i.H({class:"bm-E bm-t bm-s","data-id":t.id,"data-name":t.name,"data-premium":+t.premium,"data-correct":Number.isNaN(parseInt(l))?"0":l,"data-total":m,"data-percent":"%"==h.slice(-1)?h.slice(0,-1):"0","data-incorrect":u||0}).H({class:"bm-7",style:`background-color: rgb(${t.rgb?.map(t=>Number(t)||0).join(",")});${t.premium?e:""}`}).lt({class:"bm-u "+r,"data-state":b?"hidden":"shown","aria-label":b?`Show the color ${t.name||""} on templates.`:`Hide the color ${t.name||""} on templates.`,innerHTML:b?this.Qt.replace("{i.onclick=()=>{i.style.textDecoration="none",i.disabled=!0,"shown"==i.dataset.state?(i.innerHTML=this.Qt.replace("Number(t)||0).join(",")});`}).lt({class:"bm-u "+r,"data-state":b?"hidden":"shown","aria-label":b?`Show the color ${t.name||""} on templates.`:`Hide the color ${t.name||""} on templates.`,innerHTML:b?this.Qt.replace("{i.onclick=()=>{i.style.textDecoration="none",i.disabled=!0,"shown"==i.dataset.state?(i.innerHTML=this.Qt.replace("{const o=n.getAttribute("data-"+t),a=s.getAttribute("data-"+t),r=parseFloat(o),l=parseFloat(a),c=!isNaN(r),h=!isNaN(l);if(i?n.classList.remove("bm-B"):Number(n.getAttribute("data-total"))||n.classList.add("bm-B"),c&&h)return"ascending"===e?r-l:l-r;{const t=o.toLowerCase(),i=a.toLowerCase();return ti?"ascending"===e?1:-1:0}}),s.forEach(t=>n.appendChild(t))},A=function(t){const e=document.querySelector(`#${this.Xt}`),i=Array.from(e.children);for(const e of i){if(e.classList?.contains("bm-B"))continue;const i=e.querySelector(".bm-7 button");("hidden"!=i.dataset.state||t)&&("shown"==i.dataset.state&&t||i.click())}},W=function(){this.oe=0,this.se=0,this.ne=new Map,this.ie=new Map;for(const t of this.qt.ge){const e=t.Bt?.total??0;this.oe+=e??0;const i=t.Bt?.colors??new Map;for(const[t,e]of i){const i=Number(e)||0,n=this.ie.get(t)??0;this.ie.set(t,n+i)}const n=t.Bt?.correct??{};this.te+=Object.keys(n).length,this.ee+=Object.keys(t.Ht).length;for(const t of Object.values(n))for(const[e,i]of t){const t=Number(i)||0;this.se+=t;const n=this.ne.get(e)??0;this.ne.set(e,n+t)}}console.log(`Tiles loaded: ${this.te} / ${this.ee}`),this.se>=this.oe&&this.oe&&this.te==this.ee&&(new O).Jt(document.querySelector(`#${this.vt}`)),this.timeRemaining=new Date(30*(this.oe-this.se)*1e3+Date.now()),this.ae=a(this.timeRemaining)};var R=class extends M{constructor(t,i,n,s=void 0){super(t,i),e(this,F),this.window=null,this.vt="bm-m",this.Mt=document.body,this.fe=JSON.parse(GM_getValue("bmTemplates","{}")),this.scriptVersion=this.fe?.scriptVersion,this.schemaVersion=this.fe?.schemaVersion,this.we=void 0,this.xe=n,this.qt=s}Ct(){if(document.querySelector(`#${this.vt}`))return void document.querySelector(`#${this.vt}`).remove();let t="";document.querySelector("#bm-w")||(t=t.concat("z-index: 9001;").trim()),this.window=this.H({id:this.vt,class:"bm-N",style:t},(t,e)=>{}).gt().lt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Template Wizard"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.wt(e),e.ontouchend=()=>{e.click()}}).D().H().D().lt({class:"bm-n",textContent:"✖","aria-label":'Close window "Template Wizard"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.vt}`)?.remove()},e.ontouchend=()=>{e.click()}}).D().D().H({class:"bm-h"}).H({class:"bm-E bm-d"}).W(1,{textContent:"Template Wizard"}).D().D().F().D().H({class:"bm-E"}).W(2,{textContent:"Status"}).D().O({id:"bm-o",textContent:"Loading template storage status..."}).D().D().H({class:"bm-E bm-A"}).W(2,{textContent:"Detected templates:"}).D().D().D().D().L(this.Mt),this.xt(`#${this.vt}.bm-N`,`#${this.vt} .bm-L`),i(this,F,V).call(this),i(this,F,z).call(this)}};F=new WeakSet,V=function(){const t=this.schemaVersion.split(/[-\.\+]/),e=this.xe.split(/[-\.\+]/);let n="";t[0]==e[0]?t[1]==e[1]?(n='Template storage health: Healthy!
No futher action required. (Reason: Semantic version matches)',this.we="Good"):(n='Template storage health: Poor!
You can still use your template, but some features may not work. It is recommended that you update Blue Marble\'s template storage. (Reason: MINOR version mismatch)',this.we="Poor"):t[0]Bad!
It is guaranteed that some features are broken. You might still be able to use the template. It is HIGHLY recommended that you download all templates and update Blue Marble\'s template storage before continuing. (Reason: MAJOR version mismatch)',this.we="Bad"):(n='Template storage health: Dead!
Blue Marble can not load the template storage. (Reason: MAJOR version unknown)',this.we="Dead");const s=`
If you want to continue using your current templates, then make sure the template storage (schema) is up-to-date.
If you don't want to update the template storage, then downgrade Blue Marble to version ${r(this.scriptVersion)} to continue using your templates.
Alternatively, if you don't care about corrupting the templates listed below, you can fix any issues with the template storage by uploading a new template.`,o=function(){const t=[...document.querySelectorAll("body > div > .hidden")].filter(t=>/version:/i.test(t.textContent));if(t[0]){const e=t[0].textContent?.match(/\d+/);return e?new Date(Number(e[0])):void 0}}();let l=o?a(o):"???";this.ht("#bm-o",`${n}
Your templates were created during Blue Marble version ${r(this.scriptVersion)} with schema version ${r(this.schemaVersion)}.
The current Blue Marble version is ${r(this.version)} and requires schema version ${r(this.xe)}.
Wplace was last updated on ${l}.${"Good"!=this.we?s:""}`);const c=new M(this.name,this.version);"Dead"!=this.we&&(c.H({class:"bm-E bm-x bm-d",style:"gap: 1.5ch;"}),c.lt({textContent:"Download all templates"},(t,e)=>{e.onclick=()=>{e.disabled=!0,this.qt.ye().then(()=>{e.disabled=!1})}}).D()),"Poor"!=this.we&&"Bad"!=this.we||c.lt({textContent:`Update template storage to ${this.xe}`},(t,e)=>{e.onclick=()=>{e.disabled=!0,i(this,F,_).call(this,!0)}}).D(),c.D().L(document.querySelector("#bm-o").parentNode)},z=function(){const t=this.fe?.templates;if(Object.keys(t).length>0){const e=document.querySelector(`#${this.vt} .bm-A`),i=new M(this.name,this.version);i.H({id:"bm-r",class:"bm-E"});for(const e in t){const n=e,o=t[e];if(t.hasOwnProperty(e)){const t=n.split(" "),e=Number(t?.[0]),a=d(t?.[1]||"0",this.qt.$e),r=o.name||`Template ${e||""}`,l=o?.coords?.split(",").map(Number),c=o.pixels?.total??void 0,h=void 0,m="number"==typeof e?s(e):"???",u="number"==typeof a?s(a):"???",b="number"==typeof c?s(c):"???";i.H({class:"bm-E bm-x"}).H({class:"bm-x",style:"flex-direction: column; gap: 0;"}).H({class:"bm-1",textContent:h||"🖼️"}).D().N({textContent:`#${m}`}).D().D().H({class:"bm-x bm-0"}).W(3,{textContent:r}).D().B({textContent:`Uploaded by user #${u}`}).D().B({textContent:`Coordinates: ${l.join(", ")}`}).D().B({textContent:`Total Pixels: ${b}`}).D().D().D()}}i.D().L(e)}},_=async function(t){if(t){const t=document.querySelector(`#${this.vt} .bm-h`);t.innerHTML="",new M(this.name,this.version).H({class:"bm-E"}).H({class:"bm-E bm-d"}).W(1,{textContent:"Template Wizard"}).D().D().F().D().H({class:"bm-E"}).W(2,{textContent:"Status"}).D().O({textContent:"Updating template storage. Please wait..."}).D().D().D().L(t)}GM_deleteValue("bmCoords");const e=this.fe?.templates;if(Object.keys(e).length>0)for(const[t,i]of Object.entries(e))if(e.hasOwnProperty(t)){const t=new H({displayName:i.name,Ht:i.tiles});t.Rt();const e=await this.qt.ve(t);await this.qt.Me(e,t.displayName,t.coords)}t&&(console.log("Restarting Template Wizard..."),document.querySelector(`#${this.vt}`).remove(),new R(this.name,this.version,this.xe,this.qt).Ct())};var j,E,Y,J,X,q,Z,Q,K,tt=R;j=new WeakSet,E=function(){new G(this).Ct()},Y=async function(t,e,i){i.preventDefault();const n=await async function(t){let e="";return t&&(e=t.clipboardData.getData("text/plain")),0!=e.length||(await navigator.clipboard.readText().then(t=>{e=t}).catch(t=>{l("Failed to retrieve clipboard data using navigator! Using fallback methods...")}),0!=e.length||(e=window.clipboardData?.getData("Text"))),e}(i),s=n.split(/[^a-zA-Z0-9]+/).filter(t=>t).map(Number).filter(t=>!isNaN(t));2==s.length&&"bm-H"==e.id?(t.ht("bm-H",s?.[0]||""),t.ht("bm-I",s?.[1]||"")):1==s.length?t.ht(e.id,s?.[0]||""):(t.ht("bm-J",s?.[0]||""),t.ht("bm-K",s?.[1]||""),t.ht("bm-H",s?.[2]||""),t.ht("bm-I",s?.[3]||""))},J=new WeakSet,X=async function(){GM.setValue("bmTemplates",JSON.stringify(this.Ce))},q=async function(t){console.log("Parsing BlueMarble...");const e=t.templates;console.log(`BlueMarble length: ${Object.keys(e).length}`);const i=t?.schemaVersion,n=i.split(/[-\.\+]/),s=this.schemaVersion.split(/[-\.\+]/),o=t?.scriptVersion;console.log(`BlueMarble Template Schema: ${i}; Script Version: ${o}`),n[0]==s[0]?(n[1]!=s[1]&&new tt(this.name,this.version,this.schemaVersion,this).Ct(),this.ge=await async function({Nt:t,Te:i,ge:n}){if(Object.keys(e).length>0)for(const s in e){const o=s,a=e[s];if(console.log(`Template Key: ${o}`),e.hasOwnProperty(s)){const e=o.split(" "),s=Number(e?.[0]),r=e?.[1]||"0",l=a.name||`Template ${s||""}`,c={total:a.pixels?.total,colors:new Map(Object.entries(a.pixels?.colors||{}).map(([t,e])=>[Number(t),e]))},h=a.tiles,m={},d={},u=t*i;for(const t in h)if(console.log(t),h.hasOwnProperty(t)){const e=b(h[t]),i=new Blob([e],{type:"image/png"}),n=await createImageBitmap(i);m[t]=n;const s=new OffscreenCanvas(u,u).getContext("2d");s.drawImage(n,0,0);const o=s.getImageData(0,0,n.width,n.height);d[t]=new Uint32Array(o.data.buffer)}const p=new H({displayName:l,Dt:s||this.ge?.length||0,Lt:r||""});p.Bt=c,p.Ht=m,p.Ot=d,n.push(p),console.log(this.ge),console.log("^^^ This ^^^")}}return n}({Nt:this.Nt,Te:this.Te,ge:this.ge})):n[0]>>24&255,y=f>>>24&255,$=b.get(w)??-2,v=b.get(f)??-2;if(this.pe.get($)&&(e[i*c+h]=f),-1==$){const t=536870912;this.pe.get($)?e[i*c+h]=0:(u/o&1)==(g/o&1)?(e[i*c+h]=t,e[(i-1)*c+(h-1)]=t,e[(i-1)*c+(h+1)]=t,e[(i+1)*c+(h-1)]=t,e[(i+1)*c+(h+1)]=t):(e[i*c+h]=0,e[(i-1)*c+h]=t,e[(i+1)*c+h]=t,e[i*c+(h-1)]=t,e[i*c+(h+1)]=t)}if(!s&&x>m&&v!=$&&(d||y>m)){const t=e[i*c+h];for(const s of n){const[n,o,a]=s,r=0!=n?1!=n?t:4278190335:0;e[(i+a)*c+(h+o)]=r}}if(-1==$&&f<=m){const t=p.get($);p.set($,t?t+1:1);continue}if(x<=m||y<=m)continue;if(v!=$)continue;const M=p.get($);p.set($,M?M+1:1)}return console.log("List of template pixels that match the tile:"),console.log(p),{Be:p,Ie:e}},Q=new WeakSet,K=function(t){const e=JSON.parse(GM_getValue("bmUserSettings","{}"));e.telemetry=t,GM.setValue("bmUserSettings",JSON.stringify(e))};var et=GM_info.script.name.toString(),it=GM_info.script.version.toString();!function(t){const e=document.createElement("script");e.setAttribute("bm-S",et),e.setAttribute("bm-O","color: cornflowerblue;"),e.textContent=`(${t})();`,document.documentElement?.appendChild(e),e.remove()}(()=>{const t=document.currentScript,e=t?.getAttribute("bm-S")||"Blue Marble",i=t?.getAttribute("bm-O")||"",n=new Map;window.addEventListener("message",t=>{const{source:s,endpoint:o,blobID:a,blobData:r,blink:l}=t.data,c=Date.now()-l;if(console.groupCollapsed(`%c${e}%c: ${n.size} Recieved IMAGE message about blob "${a}"`,i,""),console.log(`Blob fetch took %c${String(Math.floor(c/6e4)).padStart(2,"0")}:${String(Math.floor(c/1e3)%60).padStart(2,"0")}.${String(c%1e3).padStart(3,"0")}%c MM:SS.mmm`,i,""),console.log(n),console.groupEnd(),"blue-marble"==s&&a&&r&&!o){const t=n.get(a);"function"==typeof t?t(r):h(`%c${e}%c: Attempted to retrieve a blob (%s) from queue, but the blobID was not a function! Skipping...`,i,"",a),n.delete(a)}});const s=window.fetch;window.fetch=async function(...t){const o=await s.apply(this,t),a=o.clone(),r=(t[0]instanceof Request?t[0]?.url:t[0])||"ignore",l=a.headers.get("content-type")||"";if(l.includes("application/json"))console.log(`%c${e}%c: Sending JSON message about endpoint "${r}"`,i,""),a.json().then(t=>{window.postMessage({source:"blue-marble",endpoint:r,jsonData:t},"*")}).catch(t=>{console.error(`%c${e}%c: Failed to parse JSON: `,i,"",t)});else if(l.includes("image/")&&!r.includes("openfreemap")&&!r.includes("maps")){const t=Date.now(),s=await a.blob();return console.log(`%c${e}%c: ${n.size} Sending IMAGE message about endpoint "${r}"`,i,""),new Promise(o=>{const l=crypto.randomUUID();n.set(l,t=>{o(new Response(t,{headers:a.headers,status:a.status,statusText:a.statusText})),console.log(`%c${e}%c: ${n.size} Processed blob "${l}"`,i,"")}),window.postMessage({source:"blue-marble",endpoint:r,blobID:l,blobData:s,blink:t})}).catch(s=>{const o=Date.now();console.error(`%c${e}%c: Failed to Promise blob!`,i,""),console.groupCollapsed(`%c${e}%c: Details of failed blob Promise:`,i,""),console.log(`Endpoint: ${r}\nThere are ${n.size} blobs processing...\nBlink: ${t.toLocaleString()}\nTime Since Blink: ${String(Math.floor(o/6e4)).padStart(2,"0")}:${String(Math.floor(o/1e3)%60).padStart(2,"0")}.${String(o%1e3).padStart(3,"0")} MM:SS.mmm`),console.error("Exception stack:",s),console.groupEnd()})}return o}});var nt=`div:has(>confetti-piece){position:absolute;inset:0;overflow:hidden;pointer-events:none}confetti-piece{position:absolute;top:-10px;width:var(--size);height:var(--size);background:currentColor;transform:translate3d(var(--x),-10vh,0) rotate(var(--rot));animation:fall var(--duration) linear var(--delay);will-change:transform;pointer-events:none}@keyframes fall{to{transform:translate3d(var(--x),110vh,0) rotate(calc(var(--rot) + 720deg))}}.bm-screenreader{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.bm-N{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000;transition:all .3s ease,transform 0s;top:75px;left:60px;width:auto;max-height:fit-content;max-width:calc(100% - 135px);font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}.bm-N.bm-G{max-width:300px}.bm-L{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:.5ch;background:url('data:image/svg+xml;utf8,') repeat;cursor:grab;width:100%;height:fit-content}.bm-L.bm-F{cursor:grabbing}.bm-N:has(.bm-L.bm-F){pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.bm-L.bm-F{pointer-events:auto}.bm-M{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle}.bm-N h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}.bm-L h1,.bm-L-text{font-size:1.2em;user-select:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-shadow:3px 0px rgba(21,48,99,.5),-3px 0px rgba(21,48,99,.5),0px 3px rgba(21,48,99,.5),0px -3px rgba(21,48,99,.5),3px 3px rgba(21,48,99,.5),-3px 3px rgba(21,48,99,.5),3px -3px rgba(21,48,99,.5),-3px -3px rgba(21,48,99,.5)}.bm-L div:has(h1){display:contents}.bm-N h2{display:inline-block;font-size:larger;font-weight:700;vertical-align:middle}.bm-N h3{display:inline-block;font-size:large;font-weight:700}.bm-E.bm-d{width:fit-content;margin-left:auto;margin-right:auto}.bm-E{margin:.5em 0}.bm-N button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}.bm-N button:hover,.bm-N button:focus-visible{background-color:#1061e5}.bm-N button:active,.bm-N button:disabled{background-color:#2e97ff}.bm-N button:disabled{text-decoration:line-through;cursor:not-allowed}.bm-n{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}.bm-N button.bm-u{background-color:unset}.bm-u.bm-b:hover,.bm-u.bm-b:focus{background-color:#ffffff2b}.bm-u.bm-b:active{background-color:#ffffff38}.bm-u.bm-c:hover,.bm-u.bm-c:focus{background-color:#0000002b}.bm-u.bm-c:active{background-color:#00000038}input[type=number].bm-v{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}input[type=number].bm-v::-webkit-outer-spin-button,input[type=number].bm-v::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}div:has(>.bm-D)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bm-D,input[type=file]{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-N select{color:#fff;background-color:#144eb9;border-radius:1em;padding:0 .5ch}.bm-N label:has(input[type=checkbox]){display:flex;width:fit-content;gap:1ch}.bm-N input[type=checkbox]{width:1em}.bm-h{overflow:hidden;transition:height .3s cubic-bezier(.4,0,.2,1)}.bm-N textarea{font-size:small;background-color:#0003;padding:0 .5ch;height:5.25em;width:100%}.bm-N a:not(:has(*)){text-decoration:underline}.bm-N small{font-size:x-small;color:#d3d3d3}.bm-N ul li{list-style:disc;margin-left:5ch}.bm-N .bm-E.bm-A{max-height:calc(80vh - 150px);overflow:auto}.bm-s{display:flex;align-content:center;justify-content:space-between;align-items:center;gap:.5ch}.bm-x{display:flex;align-content:center;justify-content:center;align-items:center;gap:.5ch}.bm-Q{white-space:pre;letter-spacing:0;line-height:1!important;font-size:1.6em;font-family:monospace}.bm-G .bm-E:not(#bm-w .bm-E){margin-top:.25em;margin-bottom:.25em}.bm-G h1:not(#bm-w h1){font-size:1em}#bm-p p svg{display:inline;height:1em;fill:#fff}#bm-y{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:center;gap:1em 3ch}#bm-p .bm-t{width:fit-content;max-width:35ch;background-color:#153063e6;border-radius:1em;padding:.5em;gap:1ch;transition:background-color .3s ease}#bm-p .bm-t:hover,#bm-p.bm-t:focus-within{background-color:#112855e6}#bm-p .bm-7{display:block;border:thick double darkslategray;width:fit-content;height:fit-content;padding:1ch}#bm-p .bm-t[data-id="-2"] .bm-7{background:conic-gradient(#a00,#aa0 16.6%,#0a0,#0aa 50%,#00a 66.6%,#a0a,#a00)}#bm-p .bm-t[data-id="-1"] .bm-7{background:url('data:image/svg+xml;utf8,') repeat;background-color:transparent!important}#bm-p .bm-t[data-id="-1"] .bm-7 svg{fill:#fff!important}#bm-p .bm-t[data-id="0"] .bm-7{background-color:transparent!important}#bm-p .bm-7 button{padding:.75em .5ch}#bm-p .bm-7 svg{width:4ch}#bm-p .bm-t>.bm-s{flex-direction:column;align-items:flex-start;gap:0}#bm-p .bm-t small{font-size:.75em}#bm-p .bm-t.bm-B{display:none}#bm-p.bm-G #bm-y{flex-direction:column;gap:.25em}#bm-p.bm-G .bm-t{width:auto;margin:0;padding:0}#bm-p.bm-G .bm-7{display:flex;width:100%;gap:.5ch;align-items:center;padding:.1em .5ch;border:none;border-radius:1em}#bm-p.bm-G .bm-7 button{padding:.5em .25ch}#bm-p.bm-G .bm-7 svg{width:3ch}#bm-p.bm-G .bm-t h2{font-size:.75em}#bm-p #bm-X{font-size:1em}#bm-N-settings div:has(>.bm-Y){width:fit-content;justify-content:flex-start}#bm-N-settings .bm-Y{display:flex;flex-direction:column;width:13%}#bm-N-settings .bm-Y span{width:fit-content;margin:auto;font-size:.7em}#bm-N-settings .bm-Y button{width:fit-content;padding:0;border-radius:0}#bm-N-settings .bm-Y svg{stroke:#333;stroke-width:.02px;width:100%;min-width:1.5ch;max-width:14.5ch}#bm-N-settings .bm-Y button:hover svg,#bm-N-settings .bm-Y button:focus svg{opacity:.9}#bm-N-settings .bm-1g{display:grid;grid-template-columns:1fr 1fr 1fr;width:25%;min-width:3ch;max-width:15ch}#bm-N-settings .bm-1g>button{width:100%;padding:0;aspect-ratio:1 / 1;background-color:#fff;border:#333 1px solid;border-radius:0;box-sizing:border-box}#bm-N-settings .bm-1g>button[data-status=Incorrect]{background-color:brown}#bm-N-settings .bm-1g>button[data-status=Template]{background-color:#2f4f4f}#bm-N-settings .bm-1g>button:hover,#bm-N-settings .bm-1g>button:focus{opacity:.8}#bm-r{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start}#bm-r>.bm-E{width:100%;justify-content:flex-start;background-color:#153063e6;border-radius:1em;padding:.5em;transition:background-color .3s ease}#bm-r>.bm-E:hover,#bm-r>.bm-E:focus-within{background-color:#112855e6}#bm-r .bm-1{height:100%;font-size:xxx-large}#bm-r .bm-0{flex-direction:column;align-items:flex-start;gap:0}`;GM_addStyle(nt);var st,ot="@font-face{font-family:'Roboto Mono';font-style:normal;font-weight:400;src:url(data:font/woff2;base64,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)format('woff2');}";ot.indexOf("@font-face")+1?(console.log("Loading Roboto Mono as a file..."),GM_addStyle(ot)):((st=document.createElement("link")).href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap",st.rel="preload",st.as="style",st.onload=function(){this.onload=null,this.rel="stylesheet"},document.head?.appendChild(st));var at=JSON.parse(GM_getValue("bmUserSettings","{}")),rt=(new class{constructor(){this.Pe=null,this.Ae=null,this.We="#bm-j"}Fe(t){return this.Ae=t,this.Pe=new MutationObserver(t=>{for(const e of t)for(const t of e.addedNodes)t instanceof HTMLElement&&t.matches?.(this.We)}),this}Ve(){return this.Pe}observe(t,e=!1,i=!1){t.observe(this.Ae,{childList:e,subtree:i})}},new class extends M{constructor(t,i){super(t,i),e(this,j),this.window=null,this.vt="bm-w",this.Mt=document.body}Ct(){document.querySelector(`#${this.vt}`)?this.yt("Main window already exists!"):(this.window=this.H({id:this.vt,class:"bm-N bm-G",style:"top: 10px; left: unset; right: 75px;"},(t,e)=>{}).gt().lt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Blue Marble"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.wt(e),e.ontouchend=()=>{e.click()}}).D().H().D().D().H({class:"bm-h"}).H({class:"bm-E"}).A({class:"bm-M",src:"data:image/png;base64,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"},(t,e)=>{const i=new Date;204==Math.floor((i.getTime()-new Date(i.getFullYear(),0,1))/864e5)+1&&(e.parentNode.style.position="relative",e.parentNode.innerHTML=e.parentNode.innerHTML+'',e.onload=()=>{(new O).Jt(document.querySelector(`#${this.vt}`))})}).D().W(1,{textContent:this.name}).D().D().F().D().H({class:"bm-E"}).B({id:"bm-q",textContent:"Droplets:"}).D().V().D().B({id:"bm-k",textContent:"Next level in..."}).D().V().D().B({textContent:"Charges: "}).ft(Date.now(),1e3,{style:"font-weight: 700;"},(t,e)=>{t.p.ze=e.id}).D().D().D().F().D().H({class:"bm-E"}).H({class:"bm-E"}).lt({class:"bm-n bm-C",style:"margin-top: 0;",innerHTML:''},(t,e)=>{e.onclick=()=>{const e=t.p?._e;e?.[0]?(t.ht("bm-J",e?.[0]||""),t.ht("bm-K",e?.[1]||""),t.ht("bm-H",e?.[2]||""),t.ht("bm-I",e?.[3]||"")):t.yt("Coordinates are malformed! Did you try clicking on the canvas first?")}}).D().dt({type:"number",id:"bm-J",class:"bm-v",placeholder:"Tl X",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,j,Y).call(this,t,e,n))}).D().dt({type:"number",id:"bm-K",class:"bm-v",placeholder:"Tl Y",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,j,Y).call(this,t,e,n))}).D().dt({type:"number",id:"bm-H",class:"bm-v",placeholder:"Px X",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,j,Y).call(this,t,e,n))}).D().dt({type:"number",id:"bm-I",class:"bm-v",placeholder:"Px Y",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,j,Y).call(this,t,e,n))}).D().D().H({class:"bm-E"}).ut({class:"bm-D",textContent:"Upload Template",accept:"image/png, image/jpeg, image/webp, image/bmp, image/gif"}).D().D().H({class:"bm-E bm-s"}).lt({textContent:"Disable","data-button-status":"shown"},(t,e)=>{e.onclick=()=>{e.disabled=!0,"shown"==e.dataset.buttonStatus?(t.p?.qt?.Ue(!1),e.dataset.buttonStatus="hidden",e.textContent="Enable",t.$t("Disabled templates!")):(t.p?.qt?.Ue(!0),e.dataset.buttonStatus="shown",e.textContent="Disable",t.$t("Enabled templates!")),e.disabled=!1}}).D().lt({textContent:"Create"},(t,e)=>{e.onclick=()=>{const e=document.querySelector(`#${this.vt} .bm-D`),i=document.querySelector("#bm-J");if(!i.checkValidity())return i.reportValidity(),void t.yt("Coordinates are malformed! Did you try clicking on the canvas first?");const n=document.querySelector("#bm-K");if(!n.checkValidity())return n.reportValidity(),void t.yt("Coordinates are malformed! Did you try clicking on the canvas first?");const s=document.querySelector("#bm-H");if(!s.checkValidity())return s.reportValidity(),void t.yt("Coordinates are malformed! Did you try clicking on the canvas first?");const o=document.querySelector("#bm-I");if(!o.checkValidity())return o.reportValidity(),void t.yt("Coordinates are malformed! Did you try clicking on the canvas first?");e?.files[0]?(t?.p?.qt.Me(e.files[0],e.files[0]?.name.replace(/\.[^/.]+$/,""),[Number(i.value),Number(n.value),Number(s.value),Number(o.value)]),t.$t("Drew to canvas!")):t.yt("No file selected!")}}).D().lt({textContent:"Filter"},(t,e)=>{e.onclick=()=>i(this,j,E).call(this)}).D().D().H({class:"bm-E"}).bt({id:this.v,placeholder:`Status: Sleeping...\nVersion: ${this.version}`,readOnly:!0}).D().D().H({class:"bm-E bm-s",style:"margin-bottom: 0; flex-direction: column;"}).H({class:"bm-s"}).lt({class:"bm-n",innerHTML:"⚙️",title:"Settings"},(t,e)=>{e.onclick=()=>{t.$.Ct()}}).D().lt({class:"bm-n",innerHTML:"🧙",title:"Template Wizard"},(t,e)=>{e.onclick=()=>{const e=t.p?.qt;new tt(this.name,this.version,e?.schemaVersion,e).Ct()}}).D().lt({class:"bm-n",innerHTML:"🎨",title:"Template Color Converter"},(t,e)=>{e.onclick=()=>{window.open("https://pepoafonso.github.io/color_converter_wplace/","_blank","noopener noreferrer")}}).D().lt({class:"bm-n",innerHTML:"🌐",title:"Official Blue Marble Website"},(t,e)=>{e.onclick=()=>{window.open("https://bluemarble.lol/","_blank","noopener noreferrer")}}).D().lt({class:"bm-n",title:"Donate to SwingTheVine",innerHTML:''},(t,e)=>{e.onclick=()=>{window.open("https://ko-fi.com/swingthevine","_blank","noopener noreferrer")}}).D().lt({class:"bm-n",innerHTML:"🤝",title:"Credits"},(t,e)=>{e.onclick=()=>{new U(this.name,this.version).Ct()}}).D().D().N({textContent:"Made by SwingTheVine",style:"margin-top: auto;"}).D().D().D().D().D().L(this.Mt),this.xt(`#${this.vt}.bm-N`,`#${this.vt} .bm-L`))}}(et,it)),lt=new class{constructor(t,i){e(this,J),this.name=t,this.version=i,this.Se=null,this.$=null,this.schemaVersion="2.0.0",this.Ge=null,this.$e="!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",this.Nt=1e3,this.Te=3,this.Ne=3,this.Kt=function(t){const e=v;e.unshift({id:-1,premium:!1,name:"Erased",rgb:[222,250,206]}),e.unshift({id:-2,premium:!1,name:"Other",rgb:[0,0,0]});const i=new Map;for(const n of e){if(0==n.id||-2==n.id)continue;const e=n.rgb[0],s=n.rgb[1],o=n.rgb[2];for(let a=-t;a<=t;a++)for(let r=-t;r<=t;r++)for(let l=-t;l<=t;l++){const t=e+a,c=s+r,h=o+l;if(t<0||t>255||c<0||c>255||h<0||h>255)continue;const m=(255<<24|h<<16|c<<8|t)>>>0;i.has(m)||i.set(m,n.id)}}return{palette:e,jt:i}}(this.Ne),this.De=null,this.Re="",this.ge=[],this.Ce=null,this.je=!0,this.Ee=null,this.pe=new Map}Ye(t){this.Se=t}k(t){this.$=t}async Je(){return{whoami:this.name.replace(" ",""),scriptVersion:this.version,schemaVersion:this.schemaVersion,templates:{}}}async Me(t,e,n){this.Ce||(this.Ce=await this.Je(),console.log("Creating JSON...")),this.Se.$t(`Creating template at ${n.join(", ")}...`);const s=new H({displayName:e,Dt:0,Lt:m(this.Ge||0,this.$e),file:t,coords:n}),o=!this.$?.kt?.flags?.includes("hl-noSkip"),a=this.$?.kt?.flags?.includes("hl-agSkip");console.log(`Should Skip: ${o}; Should Agg Skip: ${a}`);const{Ut:r,Gt:l}=await s.At(this.Nt,this.Kt,o,a);s.Ht=r;const c={total:s.Bt.total,colors:Object.fromEntries(s.Bt.colors)};this.Ce.templates[`${s.Dt} ${s.Lt}`]={name:s.displayName,coords:n.join(", "),enabled:!0,pixels:c,tiles:l},this.ge=[],this.ge.push(s),this.Se.$t(`Template created at ${n.join(", ")}!`),console.log(Object.keys(this.Ce.templates).length),console.log(this.Ce),console.log(this.ge),console.log(JSON.stringify(this.Ce)),await i(this,J,X).call(this)}Xe(){}async qe(){this.Ce||(this.Ce=await this.Je(),console.log("Creating JSON..."))}async Ze(){l("Downloading all templates..."),console.log(this.ge);for(const t of this.ge)await this.Qe(t),await n(500)}async ye(){const t=JSON.parse(GM_getValue("bmTemplates","{}"))?.templates;if(console.log(t),Object.keys(t).length>0)for(const[e,i]of Object.entries(t))t.hasOwnProperty(e)&&(await this.Qe(new H({displayName:i.name,Dt:e.split(" ")?.[0],Lt:e.split(" ")?.[1],Ht:i.tiles})),await n(500))}async Qe(t){t.Rt();const e=`${t.coords.join("-")}_${t.displayName.replaceAll(" ","-")}`,i=await this.ve(t);await GM.download({url:URL.createObjectURL(i),name:e+".png",Ke:"uniquify",onload:()=>{l(`Download of template '${e}' complete!`)},onerror:(t,i)=>{c(`Download of template '${e}' failed because ${t}! Details: ${i}`)},ontimeout:()=>{h(`Download of template '${e}' has timed out!`)}})}async ve(t){console.log(t);const e=t.Ht,i=Object.keys(e).sort(),n=await Promise.all(i.map(t=>{return i=e[t],new Promise((t,e)=>{const n=new Image;n.onload=()=>t(n),n.onerror=e,n.src="data:image/png;base64,"+i});var i}));let s=1/0,o=1/0,a=0,r=0;i.forEach((t,e)=>{const[i,l,c,h]=t.split(",").map(Number),m=n[e],d=i*this.Nt+c,u=l*this.Nt+h;s=Math.min(s,d),o=Math.min(o,u),a=Math.max(a,d+m.width/this.Te),r=Math.max(r,u+m.height/this.Te)}),console.log(`Absolute coordinates: (${s}, ${o}) and (${a}, ${r})`);const l=a-s,c=r-o,h=l*this.Te,m=c*this.Te;console.log(`Template Width: ${l}\nTemplate Height: ${c}\nCanvas Width: ${h}\nCanvas Height: ${m}`);const d=new OffscreenCanvas(h,m),u=d.getContext("2d");i.forEach((t,e)=>{const[i,a,r,l]=t.split(",").map(Number),c=n[e],h=i*this.Nt+r,m=a*this.Nt+l;console.log(`Drawing tile (${i}, ${a}, ${r}, ${l}) (${h}, ${m}) at (${h-s}, ${m-o}) on the canvas...`),u.drawImage(c,(h-s)*this.Te,(m-o)*this.Te,c.width,c.height)}),u.globalCompositeOperation="destination-over",u.drawImage(d,0,-1),u.drawImage(d,0,1),u.drawImage(d,-1,0),u.drawImage(d,1,0);const b=new OffscreenCanvas(l,c),p=b.getContext("2d");return p.imageSmoothingEnabled=!1,p.drawImage(d,0,0,l*this.Te,c*this.Te,0,0,l,c),b.convertToBlob({type:"image/png"})}async ti(t,e){if(!this.je)return t;const n=this.Nt*this.Te;e=e[0].toString().padStart(4,"0")+","+e[1].toString().padStart(4,"0"),console.log(`Searching for templates in tile: "${e}"`);const o=this.ge;console.log(o),o.sort((t,e)=>t.Dt-e.Dt),console.log(o);const a=o.map(t=>{const i=Object.keys(t.Ht).filter(t=>t.startsWith(e));if(0===i.length)return null;const n=i.map(e=>{const i=e.split(",");return{ei:t,Ft:t.Ht[e],Ot:t.Ot?.[e],ii:[i[0],i[1]],ni:[i[2],i[3]]}});return n?.[0]}).filter(Boolean);console.log(a);const r=a?.length||0;if(console.log(`templateCount = ${r}`),!(r>0))return this.Se.$t(`Sleeping\nVersion: ${this.version}`),t;{const t=s(o.filter(t=>Object.keys(t.Ht).filter(t=>t.startsWith(e)).length>0).reduce((t,e)=>t+(e.Bt.total||0),0));this.Se.$t(`Displaying ${r} template${1==r?"":"s"}.\nTotal pixels: ${t}`)}const l=await createImageBitmap(t),c=new OffscreenCanvas(n,n),h=c.getContext("2d");h.imageSmoothingEnabled=!1,h.beginPath(),h.rect(0,0,n,n),h.clip(),h.clearRect(0,0,n,n),h.drawImage(l,0,0,n,n);const m=h.getImageData(0,0,n,n),d=new Uint32Array(m.data.buffer),u=this.$?.kt?.highlight||[[2,0,0]],b=u?.[0],p=1==u?.length&&2==b?.[0]&&0==b?.[1]&&0==b?.[2];for(const t of a){console.log("Template:"),console.log(t);const n=!!t.ei.Bt?.colors?.get(-1);let s=t.Ot.slice();const o=Number(t.ni[0])*this.Te,a=Number(t.ni[1])*this.Te;if(0!=this.pe.size||n||h.drawImage(t.Ft,o,a),!s){const e=h.getImageData(o,a,t.Ft.width,t.Ft.height);s=new Uint32Array(e.data.buffer)}const r=Date.now(),{Be:l,Ie:c}=i(this,J,Z).call(this,{ke:d,De:s,Le:[o,a,t.Ft.width,t.Ft.height],He:u,Oe:p});let m=0;const b=0;for(const[t,e]of l)t!=b&&(m+=e);0==this.pe.size&&!n&&p||(console.log("Colors to filter: ",this.pe),h.drawImage(await createImageBitmap(new ImageData(new Uint8ClampedArray(c.buffer),t.Ft.width,t.Ft.height)),o,a)),console.log(`Finished calculating correct pixels & filtering colors for the tile ${e} in ${(Date.now()-r)/1e3} seconds!\nThere are ${m} correct pixels.`),void 0===t.ei.Bt.correct&&(t.ei.Bt.correct={}),t.ei.Bt.correct[e]=l}return await c.convertToBlob({type:"image/png"})}si(t){console.log("Importing JSON..."),console.log(t),"BlueMarble"==t?.whoami&&i(this,J,q).call(this,t)}Ue(t){this.je=t}}(et,it),ct=new class{constructor(t){this.qt=t,this.oi=!1,this.ze="",this._e=[],this.ai=[]}ri(t){window.addEventListener("message",async e=>{const i=e.data,n=i.jsonData;if(!i||"blue-marble"!==i.source)return;if(!i.endpoint)return;const o=i.endpoint?.split("?")[0].split("/").filter(t=>t&&isNaN(Number(t))).filter(t=>t&&!t.includes(".")).pop();switch(console.log('%cBlue Marble%c: Recieved message about "%s"',"color: cornflowerblue;","",o),o){case"me":if(n.status&&"2"!=n.status?.toString()[0])return void t.yt("You are not logged in or Wplace is offline!\nCould not fetch userdata.");const e=Math.ceil(Math.pow(Math.floor(n.level)*Math.pow(30,.65),1/.65)-n.pixelsPainted);if(console.log(n.id),(n.id||0===n.id)&&console.log(m(n.id,"!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~")),this.qt.Ge=n.id,0!=this.ze.length){const t=document.querySelector("#"+this.ze);if(t){const e=n.charges;t.dataset.endDate=Date.now()+(e.max-e.count)*e.cooldownMs}}t.ht("bm-q",`Droplets: ${s(n.droplets)}`),t.ht("bm-k",`Next level in ${s(e)} pixel${1==e?"":"s"}`);break;case"pixel":const o=i.endpoint.split("?")[0].split("/").filter(t=>t&&!isNaN(Number(t))),l=new URLSearchParams(i.endpoint.split("?")[1]),c=[l.get("x"),l.get("y")];if(this._e.length&&(!o.length||!c.length))return void t.yt("Coordinates are malformed!\nDid you try clicking the canvas first?");this._e=[...o,...c];const h=(a=o,r=c,[parseInt(a[0])%4*1e3+parseInt(r[0]),parseInt(a[1])%4*1e3+parseInt(r[1])]),d=document.querySelectorAll("span");for(const t of d)if(t.textContent.trim().includes(`${h[0]}, ${h[1]}`)){let e=document.querySelector("#bm-j");const i=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${c[0]}, Px Y: ${c[1]})`;e?e.textContent=i:(e=document.createElement("span"),e.id="bm-j",e.textContent=i,e.style="margin-left: calc(var(--spacing)*3); font-size: small;",t.parentNode.parentNode.insertAdjacentElement("afterend",e))}break;case"tile":case"tiles":let u=i.endpoint.split("/");u=[parseInt(u[u.length-2]),parseInt(u[u.length-1].replace(".png",""))];const b=i.blobID,p=i.blobData,g=Date.now(),f=await this.qt.ti(p,u);console.log(`Finished loading the tile in ${(Date.now()-g)/1e3} seconds!`),window.postMessage({source:"blue-marble",blobID:b,blobData:f,blink:i.blink});break;case"robots":this.oi="false"==n.userscript?.toString().toLowerCase();break}var a,r})}async li(t){console.log("Sending heartbeat to telemetry server...");let e=GM_getValue("bmUserSettings","{}");if(e=JSON.parse(e),!e||!e.telemetry||!e.uuid)return void console.log("Telemetry is disabled, not sending heartbeat.");const i=navigator.userAgent;let n=await this.ci(i),s=this.hi(i);GM_xmlhttpRequest({method:"POST",url:"https://telemetry.thebluecorner.net/heartbeat",headers:{"Content-Type":"application/json"},data:JSON.stringify({uuid:e.uuid,version:t,browser:n,os:s}),onload:t=>{200!==t.status&&c("Failed to send heartbeat:",t.statusText)},onerror:t=>{c("Error sending heartbeat:",t)}})}async ci(t=navigator.userAgent){return(t=t||"").includes("OPR/")||t.includes("Opera")?"Opera":t.includes("Edg/")?"Edge":t.includes("Vivaldi")?"Vivaldi":t.includes("YaBrowser")?"Yandex":t.includes("Kiwi")?"Kiwi":t.includes("Brave")?"Brave":t.includes("Firefox/")?"Firefox":t.includes("Chrome/")?"Chrome":t.includes("Safari/")?"Safari":navigator.brave&&"function"==typeof navigator.brave.isBrave&&await navigator.brave.isBrave()?"Brave":"Unknown"}hi(t=navigator.userAgent){return/Windows NT 11/i.test(t=t||"")?"Windows 11":/Windows NT 10/i.test(t)?"Windows 10":/Windows NT 6\.3/i.test(t)?"Windows 8.1":/Windows NT 6\.2/i.test(t)?"Windows 8":/Windows NT 6\.1/i.test(t)?"Windows 7":/Windows NT 6\.0/i.test(t)?"Windows Vista":/Windows NT 5\.1|Windows XP/i.test(t)?"Windows XP":/Mac OS X 10[_\.]15/i.test(t)?"macOS Catalina":/Mac OS X 10[_\.]14/i.test(t)?"macOS Mojave":/Mac OS X 10[_\.]13/i.test(t)?"macOS High Sierra":/Mac OS X 10[_\.]12/i.test(t)?"macOS Sierra":/Mac OS X 10[_\.]11/i.test(t)?"OS X El Capitan":/Mac OS X 10[_\.]10/i.test(t)?"OS X Yosemite":/Mac OS X 10[_\.]/i.test(t)?"macOS":/Android/i.test(t)?"Android":/iPhone|iPad|iPod/i.test(t)?"iOS":/Linux/i.test(t)?"Linux":"Unknown"}}(lt),ht=new class extends L{constructor(t,i,n){var s;super(t,i),e(this,C),this.kt=n,(s=this.kt).flags??(s.flags=[]),this.mi=structuredClone(this.kt),this.di="bmUserSettings",this.ui=5e3,this.bi=0,setInterval(this.pi.bind(this),this.ui)}async pi(){const t=JSON.stringify(this.kt);t!=JSON.stringify(this.mi)&&Date.now()-this.bi>this.ui&&(await GM.setValue(this.di,t),this.mi=structuredClone(this.kt),this.bi=Date.now(),console.log(t))}gi(t,e=void 0){const i=this.kt?.flags?.indexOf(t)??-1;-1!=i&&!0!==e?this.kt?.flags?.splice(i,1):-1==i&&!1!==e&&this.kt?.flags?.push(t)}Tt(){const t='',e='',n=this.kt?.highlight??[[1,0,1],[2,0,0],[1,-1,0],[1,1,0],[1,0,-1]];this.window=this.H({class:"bm-E"}).W(2,{textContent:"Pixel Highlight"}).D().F().D().H({class:"bm-E",style:"margin-left: 1.5ch;"}).R({textContent:"Highlight transparent pixels"},(t,e,i)=>{i.checked=!this.kt?.flags?.includes("hl-noTrans"),i.onchange=t=>this.gi("hl-noTrans",!t.target.checked)}).D().O({id:"bm-Z",textContent:"Choose a preset:",style:"font-weight: 700;"}).D().H({class:"bm-x",role:"group","aria-labelledby":"bm-Z"}).H({class:"bm-Y"}).B({textContent:"None"}).D().lt({innerHTML:t,"aria-label":'Preset "None"'},(t,e)=>{e.onclick=()=>i(this,C,S).call(this,"None")}).D().D().H({class:"bm-Y"}).B({textContent:"Cross"}).D().lt({innerHTML:e,"aria-label":'Preset "Cross Shape"'},(t,e)=>{e.onclick=()=>i(this,C,S).call(this,"Cross")}).D().D().H({class:"bm-Y"}).B({textContent:"X"}).D().lt({innerHTML:e.replace('d="M1,0H2V1H3V2H2V3H1V2H0V1H1Z"','d="M0,0V1H3V0H2V3H3V2H0V3H1V0Z"'),"aria-label":'Preset "X Shape"'},(t,e)=>{e.onclick=()=>i(this,C,S).call(this,"X")}).D().D().H({class:"bm-Y"}).B({textContent:"Full"}).D().lt({innerHTML:t.replace("#fff","#2f4f4f"),"aria-label":'Preset "Full Template"'},(t,e)=>{e.onclick=()=>i(this,C,S).call(this,"Full")}).D().D().D().O({id:"bm-14",textContent:"Create a custom pattern:",style:"font-weight: 700;"}).D().H({class:"bm-1g",role:"group","aria-labelledby":"bm-14"});for(let t=-1;t<=1;t++)for(let e=-1;e<=1;e++){const s=n[n.findIndex(([,i,n])=>i==e&&n==t)]?.[0]??0;let o="Disabled";1==s?o="Incorrect":2==s&&(o="Template"),this.window=this.lt({"data-status":o,"aria-label":`Sub-pixel ${o.toLowerCase()}`},(n,s)=>{s.onclick=()=>i(this,C,T).call(this,s,[e,t])}).D()}this.window=this.D().D().D()}St(){this.window=this.H({class:"bm-E"}).W(2,{textContent:"Pixel Highlight"}).D().F().D().H({class:"bm-E",style:"margin-left: 1.5ch;"}).R({textContent:"Template creation should skip transparent tiles"},(t,e,i)=>{i.checked=!this.kt?.flags?.includes("hl-noSkip"),i.onchange=t=>this.gi("hl-noSkip",!t.target.checked)}).D().R({innerHTML:"Experimental: Template creation should aggressively skip transparent tiles"},(t,e,i)=>{i.checked=this.kt?.flags?.includes("hl-agSkip"),i.onchange=t=>this.gi("hl-agSkip",t.target.checked)}).D().D().D()}}(et,it,at);rt.k(ht),rt.S(ct),lt.Ye(rt),lt.k(ht);var mt=JSON.parse(GM_getValue("bmTemplates","{}"));if(console.log(mt),lt.si(mt),console.log(at),console.log(Object.keys(at).length),0==Object.keys(at).length){const t=crypto.randomUUID();console.log(t),GM.setValue("bmUserSettings",JSON.stringify({uuid:t}))}setInterval(()=>ct.li(it),18e5);var dt=at?.telemetry;if(console.log(`Telemetry is ${!(null==dt)}`),null==dt||dt>1){const t=new class extends M{constructor(t,i,n,s){super(t,i),e(this,Q),this.window=null,this.vt="bm-g",this.Mt=document.body,this.fi=n,this.uuid=s}async Ct(){if(document.querySelector(`#${this.vt}`))return void this.yt("Telemetry window already exists!");const t=await this.p.ci(navigator.userAgent),e=this.p.hi(navigator.userAgent);this.window=this.H({id:this.vt,class:"bm-N",style:"height: 80vh; z-index: 9998;"}).H({class:"bm-h"}).H({class:"bm-E bm-d"}).W(1,{textContent:`${this.name} Telemetry`}).D().D().F().D().H({class:"bm-E bm-x",style:"gap: 1.5ch; flex-wrap: wrap;"}).lt({textContent:"Enable Telemetry"},(t,e)=>{e.onclick=()=>{i(this,Q,K).call(this,this.fi);const t=document.getElementById(this.vt);t?.remove()}}).D().lt({textContent:"Disable Telemetry"},(t,e)=>{e.onclick=()=>{i(this,Q,K).call(this,0);const t=document.getElementById(this.vt);t?.remove()}}).D().lt({textContent:"More Information"},(t,e)=>{e.onclick=()=>{window.open("https://github.com/SwingTheVine/Wplace-TelemetryServer#telemetry-data","_blank","noopener noreferrer")}}).D().D().H({class:"bm-E bm-A"}).H({class:"bm-E"}).W(2,{textContent:"Legal"}).D().O({textContent:`We collect anonymous telemetry data such as your browser, OS, and script version to make the experience better for everyone. The data is never shared personally. The data is never sold. You can turn this off by pressing the "Disable" button, but keeping it on helps us improve features and reliability faster. Thank you for supporting ${this.name}!`}).D().D().F().D().H({class:"bm-E"}).W(2,{textContent:"Non-Legal Summary"}).D().O({innerHTML:'You can disable telemetry by pressing the "Disable" button. If you would like to read more about what information we collect, press the "More Information" button.
This is the data stored on our servers:'}).D().X().Z({innerHTML:`A unique identifier (UUIDv4) generated by Blue Marble. This enables our telemetry to function without tracking your actual user ID.
Your UUID is: ${r(this.uuid)}`}).D().Z({innerHTML:`The version of Blue Marble you are using.
Your version is: ${r(this.version)}`}).D().Z({innerHTML:`Your browser type, which is used to determine Blue Marble outages and browser popularity.
Your browser type is: ${r(t)}`}).D().Z({innerHTML:`Your OS type, which is used to determine Blue Marble outages and OS popularity.
Your OS type is: ${r(e)}`}).D().Z({innerHTML:"The date and time that Blue Marble sent the telemetry information."}).D().D().O({innerHTML:'All of the data mentioned above is aggregated every hour. This means every hour, anything that could even remotly be considered "personal data" is deleted from our server. Here, "aggregated" data means things like "42 people used Blue Marble on Google Chrome this hour", which can\'t be used to identify anyone in particular.'}).D().D().D().D().D().L(this.Mt)}}(et,it,1,at?.uuid);t.S(ct),t.Ct()}rt.Ct(),ct.ri(rt),new MutationObserver((t,e)=>{const i=document.querySelector("#color-1");if(!i)return;let n=document.querySelector("#bm-z");if(!n){n=document.createElement("button"),n.id="bm-z",n.textContent="Move ↑",n.className="btn btn-soft",n.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 ↑"};const t=i.parentNode.parentNode.parentNode.parentNode.querySelector("h2");t.parentNode?.appendChild(n)}}).observe(document.body,{childList:!0,subtree:!0}),l(`%c${et}%c (${it}) userscript has loaded!`,"color: cornflowerblue;","")})(); \ No newline at end of file diff --git a/dist/BlueMarble.user.css b/dist/BlueMarble.user.css index 9978f90..6fa35d6 100644 --- a/dist/BlueMarble.user.css +++ b/dist/BlueMarble.user.css @@ -1 +1 @@ -#bm-p p svg{display:inline;height:1em;fill:#fff}#bm-y{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:center;gap:1em 3ch}.bm-t{width:fit-content;max-width:35ch;background-color:#153063e6;border-radius:1em;padding:.5em;gap:1ch;transition:background-color .3s ease}.bm-t:hover,.bm-t:focus-within{background-color:#112855e6}.bm-7{display:block;border:thick double darkslategray;width:fit-content;height:fit-content;padding:1ch}.bm-t[data-id="-2"] .bm-7{background:conic-gradient(#a00,#aa0 16.6%,#0a0,#0aa 50%,#00a 66.6%,#a0a,#a00)}.bm-t[data-id="-1"] .bm-7{background:url('data:image/svg+xml;utf8,') repeat;background-color:transparent!important}.bm-t[data-id="-1"] .bm-7 svg{fill:#fff!important}.bm-t[data-id="0"] .bm-7{background-color:transparent!important}#bm-p .bm-7 button{padding:.75em .5ch}.bm-7 svg{width:4ch}.bm-t>.bm-s{flex-direction:column;align-items:flex-start;gap:0}.bm-t small{font-size:.75em}#bm-p .bm-t.bm-B{display:none}.bm-G #bm-y{flex-direction:column;gap:.25em}.bm-G .bm-t{width:auto;margin:0;padding:0}.bm-G .bm-7{display:flex;width:100%;gap:.5ch;align-items:center;padding:.1em .5ch;border:none;border-radius:1em}#bm-p.bm-G .bm-7 button{padding:.5em .25ch}.bm-G .bm-7 svg{width:3ch}.bm-G .bm-t h2{font-size:.75em}#bm-r{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start}#bm-r>.bm-E{width:100%;justify-content:flex-start;background-color:#153063e6;border-radius:1em;padding:.5em;transition:background-color .3s ease}#bm-r>.bm-E:hover,#bm-r>.bm-E:focus-within{background-color:#112855e6}#bm-r .bm-1{height:100%;font-size:xxx-large}#bm-r .bm-0{flex-direction:column;align-items:flex-start;gap:0}div:has(>confetti-piece){position:absolute;inset:0;overflow:hidden;pointer-events:none}confetti-piece{position:absolute;top:-10px;width:var(--size);height:var(--size);background:currentColor;transform:translate3d(var(--x),-10vh,0) rotate(var(--rot));animation:fall var(--duration) linear var(--delay);will-change:transform;pointer-events:none}@keyframes fall{to{transform:translate3d(var(--x),110vh,0) rotate(calc(var(--rot) + 720deg))}}.bm-screenreader{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.bm-N{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000;transition:all .3s ease,transform 0s;top:75px;left:60px;width:auto;max-height:fit-content;max-width:calc(100% - 135px);font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}.bm-N.bm-G{max-width:300px}.bm-L{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:.5ch;background:url('data:image/svg+xml;utf8,') repeat;cursor:grab;width:100%;height:fit-content}.bm-L.bm-F{cursor:grabbing}.bm-N:has(.bm-L.bm-F){pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.bm-L.bm-F{pointer-events:auto}.bm-M{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle}.bm-N h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}.bm-L h1{font-size:1.2em;user-select:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-shadow:3px 0px rgba(21,48,99,.5),-3px 0px rgba(21,48,99,.5),0px 3px rgba(21,48,99,.5),0px -3px rgba(21,48,99,.5),3px 3px rgba(21,48,99,.5),-3px 3px rgba(21,48,99,.5),3px -3px rgba(21,48,99,.5),-3px -3px rgba(21,48,99,.5)}.bm-L div:has(h1){display:contents}.bm-N h2{display:inline-block;font-size:larger;font-weight:700;vertical-align:middle}.bm-N h3{display:inline-block;font-size:large;font-weight:700}.bm-E.bm-d{width:fit-content;margin-left:auto;margin-right:auto}.bm-E{margin:.5em 0}.bm-N button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}.bm-N button:hover,.bm-N button:focus-visible{background-color:#1061e5}.bm-N button:active,.bm-N button:disabled{background-color:#2e97ff}.bm-N button:disabled{text-decoration:line-through;cursor:not-allowed}.bm-n{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}.bm-N button.bm-u{background-color:unset}.bm-u.bm-b:hover,.bm-u.bm-b:focus{background-color:#ffffff2b}.bm-u.bm-b:active{background-color:#ffffff38}.bm-u.bm-c:hover,.bm-u.bm-c:focus{background-color:#0000002b}.bm-u.bm-c:active{background-color:#00000038}input[type=number].bm-v{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}input[type=number].bm-v::-webkit-outer-spin-button,input[type=number].bm-v::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}div:has(>.bm-D)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bm-D,input[type=file]{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-N select{color:#fff;background-color:#144eb9;border-radius:1em;padding:0 .5ch}.bm-N label:has(input[type=checkbox]){display:flex;width:fit-content;gap:1ch}.bm-N input[type=checkbox]{width:1em}.bm-h{overflow:hidden;transition:height .3s cubic-bezier(.4,0,.2,1)}.bm-N textarea{font-size:small;background-color:#0003;padding:0 .5ch;height:5.25em;width:100%}.bm-N a:not(:has(*)){text-decoration:underline}.bm-N small{font-size:x-small;color:#d3d3d3}.bm-N ul li{list-style:disc;margin-left:5ch}.bm-N .bm-E.bm-A{max-height:calc(80vh - 150px);overflow:auto}.bm-s{display:flex;align-content:center;justify-content:space-between;align-items:center;gap:.5ch}.bm-x{display:flex;align-content:center;justify-content:center;align-items:center;gap:.5ch}.bm-Q{white-space:pre;letter-spacing:0;line-height:1!important;font-size:1.6em;font-family:monospace}.bm-E:not(#bm-w .bm-E){margin:.25em 0}.bm-G h1:not(#bm-w h1){font-size:1em} +div:has(>confetti-piece){position:absolute;inset:0;overflow:hidden;pointer-events:none}confetti-piece{position:absolute;top:-10px;width:var(--size);height:var(--size);background:currentColor;transform:translate3d(var(--x),-10vh,0) rotate(var(--rot));animation:fall var(--duration) linear var(--delay);will-change:transform;pointer-events:none}@keyframes fall{to{transform:translate3d(var(--x),110vh,0) rotate(calc(var(--rot) + 720deg))}}.bm-screenreader{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.bm-N{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000;transition:all .3s ease,transform 0s;top:75px;left:60px;width:auto;max-height:fit-content;max-width:calc(100% - 135px);font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}.bm-N.bm-G{max-width:300px}.bm-L{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:.5ch;background:url('data:image/svg+xml;utf8,') repeat;cursor:grab;width:100%;height:fit-content}.bm-L.bm-F{cursor:grabbing}.bm-N:has(.bm-L.bm-F){pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.bm-L.bm-F{pointer-events:auto}.bm-M{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle}.bm-N h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}.bm-L h1,.bm-L-text{font-size:1.2em;user-select:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-shadow:3px 0px rgba(21,48,99,.5),-3px 0px rgba(21,48,99,.5),0px 3px rgba(21,48,99,.5),0px -3px rgba(21,48,99,.5),3px 3px rgba(21,48,99,.5),-3px 3px rgba(21,48,99,.5),3px -3px rgba(21,48,99,.5),-3px -3px rgba(21,48,99,.5)}.bm-L div:has(h1){display:contents}.bm-N h2{display:inline-block;font-size:larger;font-weight:700;vertical-align:middle}.bm-N h3{display:inline-block;font-size:large;font-weight:700}.bm-E.bm-d{width:fit-content;margin-left:auto;margin-right:auto}.bm-E{margin:.5em 0}.bm-N button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}.bm-N button:hover,.bm-N button:focus-visible{background-color:#1061e5}.bm-N button:active,.bm-N button:disabled{background-color:#2e97ff}.bm-N button:disabled{text-decoration:line-through;cursor:not-allowed}.bm-n{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}.bm-N button.bm-u{background-color:unset}.bm-u.bm-b:hover,.bm-u.bm-b:focus{background-color:#ffffff2b}.bm-u.bm-b:active{background-color:#ffffff38}.bm-u.bm-c:hover,.bm-u.bm-c:focus{background-color:#0000002b}.bm-u.bm-c:active{background-color:#00000038}input[type=number].bm-v{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}input[type=number].bm-v::-webkit-outer-spin-button,input[type=number].bm-v::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}div:has(>.bm-D)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bm-D,input[type=file]{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-N select{color:#fff;background-color:#144eb9;border-radius:1em;padding:0 .5ch}.bm-N label:has(input[type=checkbox]){display:flex;width:fit-content;gap:1ch}.bm-N input[type=checkbox]{width:1em}.bm-h{overflow:hidden;transition:height .3s cubic-bezier(.4,0,.2,1)}.bm-N textarea{font-size:small;background-color:#0003;padding:0 .5ch;height:5.25em;width:100%}.bm-N a:not(:has(*)){text-decoration:underline}.bm-N small{font-size:x-small;color:#d3d3d3}.bm-N ul li{list-style:disc;margin-left:5ch}.bm-N .bm-E.bm-A{max-height:calc(80vh - 150px);overflow:auto}.bm-s{display:flex;align-content:center;justify-content:space-between;align-items:center;gap:.5ch}.bm-x{display:flex;align-content:center;justify-content:center;align-items:center;gap:.5ch}.bm-Q{white-space:pre;letter-spacing:0;line-height:1!important;font-size:1.6em;font-family:monospace}.bm-G .bm-E:not(#bm-w .bm-E){margin-top:.25em;margin-bottom:.25em}.bm-G h1:not(#bm-w h1){font-size:1em}#bm-p p svg{display:inline;height:1em;fill:#fff}#bm-y{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:center;gap:1em 3ch}#bm-p .bm-t{width:fit-content;max-width:35ch;background-color:#153063e6;border-radius:1em;padding:.5em;gap:1ch;transition:background-color .3s ease}#bm-p .bm-t:hover,#bm-p.bm-t:focus-within{background-color:#112855e6}#bm-p .bm-7{display:block;border:thick double darkslategray;width:fit-content;height:fit-content;padding:1ch}#bm-p .bm-t[data-id="-2"] .bm-7{background:conic-gradient(#a00,#aa0 16.6%,#0a0,#0aa 50%,#00a 66.6%,#a0a,#a00)}#bm-p .bm-t[data-id="-1"] .bm-7{background:url('data:image/svg+xml;utf8,') repeat;background-color:transparent!important}#bm-p .bm-t[data-id="-1"] .bm-7 svg{fill:#fff!important}#bm-p .bm-t[data-id="0"] .bm-7{background-color:transparent!important}#bm-p .bm-7 button{padding:.75em .5ch}#bm-p .bm-7 svg{width:4ch}#bm-p .bm-t>.bm-s{flex-direction:column;align-items:flex-start;gap:0}#bm-p .bm-t small{font-size:.75em}#bm-p .bm-t.bm-B{display:none}#bm-p.bm-G #bm-y{flex-direction:column;gap:.25em}#bm-p.bm-G .bm-t{width:auto;margin:0;padding:0}#bm-p.bm-G .bm-7{display:flex;width:100%;gap:.5ch;align-items:center;padding:.1em .5ch;border:none;border-radius:1em}#bm-p.bm-G .bm-7 button{padding:.5em .25ch}#bm-p.bm-G .bm-7 svg{width:3ch}#bm-p.bm-G .bm-t h2{font-size:.75em}#bm-p #bm-X{font-size:1em}#bm-N-settings div:has(>.bm-Y){width:fit-content;justify-content:flex-start}#bm-N-settings .bm-Y{display:flex;flex-direction:column;width:13%}#bm-N-settings .bm-Y span{width:fit-content;margin:auto;font-size:.7em}#bm-N-settings .bm-Y button{width:fit-content;padding:0;border-radius:0}#bm-N-settings .bm-Y svg{stroke:#333;stroke-width:.02px;width:100%;min-width:1.5ch;max-width:14.5ch}#bm-N-settings .bm-Y button:hover svg,#bm-N-settings .bm-Y button:focus svg{opacity:.9}#bm-N-settings .bm-1g{display:grid;grid-template-columns:1fr 1fr 1fr;width:25%;min-width:3ch;max-width:15ch}#bm-N-settings .bm-1g>button{width:100%;padding:0;aspect-ratio:1 / 1;background-color:#fff;border:#333 1px solid;border-radius:0;box-sizing:border-box}#bm-N-settings .bm-1g>button[data-status=Incorrect]{background-color:brown}#bm-N-settings .bm-1g>button[data-status=Template]{background-color:#2f4f4f}#bm-N-settings .bm-1g>button:hover,#bm-N-settings .bm-1g>button:focus{opacity:.8}#bm-r{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start}#bm-r>.bm-E{width:100%;justify-content:flex-start;background-color:#153063e6;border-radius:1em;padding:.5em;transition:background-color .3s ease}#bm-r>.bm-E:hover,#bm-r>.bm-E:focus-within{background-color:#112855e6}#bm-r .bm-1{height:100%;font-size:xxx-large}#bm-r .bm-0{flex-direction:column;align-items:flex-start;gap:0} diff --git a/dist/BlueMarble.user.js b/dist/BlueMarble.user.js index 8b0afb3..8c300a1 100644 --- a/dist/BlueMarble.user.js +++ b/dist/BlueMarble.user.js @@ -2,7 +2,7 @@ // @name Blue Marble // @name:en Blue Marble // @namespace https://github.com/SwingTheVine/ -// @version 0.91.0 +// @version 0.91.102 // @description A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features. // @description:en A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features. // @author SwingTheVine @@ -39,4 +39,4 @@ The "Blue Marble" image is owned by NASA. */ -(()=>{var t=t=>{throw TypeError(t)},e=(e,i,n)=>i.has(e)?t("Cannot add the same private member more than once"):i instanceof WeakSet?i.add(e):i.set(e,n),i=(e,i,n)=>(((e,i)=>{i.has(e)||t("Cannot access private method")})(e,i),n);function n(t){return new Promise(e=>setTimeout(e,t))}function s(t){return(new Intl.NumberFormat).format(t)}function o(t){return new Intl.NumberFormat(void 0,{style:"percent",t:2,i:2}).format(t)}function a(t){return t.toLocaleString(void 0,{o:"long",l:"numeric",h:"2-digit",m:"2-digit",u:"2-digit"})}function r(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}function l(...t){(0,console.log)(...t)}function c(...t){(0,console.error)(...t)}function h(...t){(0,console.warn)(...t)}function m(t,e){if(0===t)return e[0];let i="";const n=e.length;for(;t>0;)i=e[t%n]+i,t=Math.floor(t/n);return i}function d(t,e){let i=0;const n=e.length;for(const s of t){const t=e.indexOf(s);-1==t&&c(`Invalid character '${s}' encountered whilst decoding! Is the decode alphabet/base incorrect?`),i=i*n+t}return i}function u(t){let e="";for(let i=0;i(t/=255)<=.03928?t/12.92:Math.pow((t+.055)/1.055,2.4));return.2126*e[0]+.7152*e[1]+.0722*e[2]}function f(t,e,i){return Array.isArray(t)&&([t,e,i]=t),(1<<24|t<<16|e<<8|i).toString(16).slice(1)}var w,g,y,x,v,M=[{id:0,premium:!1,name:"Transparent",rgb:[0,0,0]},{id:1,premium:!1,name:"Black",rgb:[0,0,0]},{id:2,premium:!1,name:"Dark Gray",rgb:[60,60,60]},{id:3,premium:!1,name:"Gray",rgb:[120,120,120]},{id:4,premium:!1,name:"Light Gray",rgb:[210,210,210]},{id:5,premium:!1,name:"White",rgb:[255,255,255]},{id:6,premium:!1,name:"Deep Red",rgb:[96,0,24]},{id:7,premium:!1,name:"Red",rgb:[237,28,36]},{id:8,premium:!1,name:"Orange",rgb:[255,127,39]},{id:9,premium:!1,name:"Gold",rgb:[246,170,9]},{id:10,premium:!1,name:"Yellow",rgb:[249,221,59]},{id:11,premium:!1,name:"Light Yellow",rgb:[255,250,188]},{id:12,premium:!1,name:"Dark Green",rgb:[14,185,104]},{id:13,premium:!1,name:"Green",rgb:[19,230,123]},{id:14,premium:!1,name:"Light Green",rgb:[135,255,94]},{id:15,premium:!1,name:"Dark Teal",rgb:[12,129,110]},{id:16,premium:!1,name:"Teal",rgb:[16,174,166]},{id:17,premium:!1,name:"Light Teal",rgb:[19,225,190]},{id:18,premium:!1,name:"Dark Blue",rgb:[40,80,158]},{id:19,premium:!1,name:"Blue",rgb:[64,147,228]},{id:20,premium:!1,name:"Cyan",rgb:[96,247,242]},{id:21,premium:!1,name:"Indigo",rgb:[107,80,246]},{id:22,premium:!1,name:"Light Indigo",rgb:[153,177,251]},{id:23,premium:!1,name:"Dark Purple",rgb:[120,12,153]},{id:24,premium:!1,name:"Purple",rgb:[170,56,185]},{id:25,premium:!1,name:"Light Purple",rgb:[224,159,249]},{id:26,premium:!1,name:"Dark Pink",rgb:[203,0,122]},{id:27,premium:!1,name:"Pink",rgb:[236,31,128]},{id:28,premium:!1,name:"Light Pink",rgb:[243,141,169]},{id:29,premium:!1,name:"Dark Brown",rgb:[104,70,52]},{id:30,premium:!1,name:"Brown",rgb:[149,104,42]},{id:31,premium:!1,name:"Beige",rgb:[248,178,119]},{id:32,premium:!0,name:"Medium Gray",rgb:[170,170,170]},{id:33,premium:!0,name:"Dark Red",rgb:[165,14,30]},{id:34,premium:!0,name:"Light Red",rgb:[250,128,114]},{id:35,premium:!0,name:"Dark Orange",rgb:[228,92,26]},{id:36,premium:!0,name:"Light Tan",rgb:[214,181,148]},{id:37,premium:!0,name:"Dark Goldenrod",rgb:[156,132,49]},{id:38,premium:!0,name:"Goldenrod",rgb:[197,173,49]},{id:39,premium:!0,name:"Light Goldenrod",rgb:[232,212,95]},{id:40,premium:!0,name:"Dark Olive",rgb:[74,107,58]},{id:41,premium:!0,name:"Olive",rgb:[90,148,74]},{id:42,premium:!0,name:"Light Olive",rgb:[132,197,115]},{id:43,premium:!0,name:"Dark Cyan",rgb:[15,121,159]},{id:44,premium:!0,name:"Light Cyan",rgb:[187,250,242]},{id:45,premium:!0,name:"Light Blue",rgb:[125,199,255]},{id:46,premium:!0,name:"Dark Indigo",rgb:[77,49,184]},{id:47,premium:!0,name:"Dark Slate Blue",rgb:[74,66,132]},{id:48,premium:!0,name:"Slate Blue",rgb:[122,113,196]},{id:49,premium:!0,name:"Light Slate Blue",rgb:[181,174,241]},{id:50,premium:!0,name:"Light Brown",rgb:[219,164,99]},{id:51,premium:!0,name:"Dark Beige",rgb:[209,128,81]},{id:52,premium:!0,name:"Light Beige",rgb:[255,197,165]},{id:53,premium:!0,name:"Dark Peach",rgb:[155,82,73]},{id:54,premium:!0,name:"Peach",rgb:[209,128,120]},{id:55,premium:!0,name:"Light Peach",rgb:[250,182,164]},{id:56,premium:!0,name:"Dark Tan",rgb:[123,99,82]},{id:57,premium:!0,name:"Tan",rgb:[156,132,107]},{id:58,premium:!0,name:"Dark Slate",rgb:[51,57,65]},{id:59,premium:!0,name:"Slate",rgb:[109,117,141]},{id:60,premium:!0,name:"Light Slate",rgb:[179,185,209]},{id:61,premium:!0,name:"Dark Stone",rgb:[109,100,63]},{id:62,premium:!0,name:"Stone",rgb:[148,140,107]},{id:63,premium:!0,name:"Light Stone",rgb:[205,197,158]}],C=class{constructor({displayName:t="My template",p:i=0,v:n="",url:s="",file:o=null,coords:a=null,M:r=null,C:l={},$:c=1e3}={}){e(this,w),this.displayName=t,this.p=i,this.v=n,this.url=s,this.file=o,this.coords=a,this.M=r,this.C=l,this.$=c,this.T={total:0,colors:new Map}}async S(t,e){const n=await createImageBitmap(this.file),s=n.width,o=n.height;this.$=t;const a={},r={},l=new OffscreenCanvas(this.$,this.$),c=l.getContext("2d",{willReadFrequently:!0});l.width=s,l.height=o,c.imageSmoothingEnabled=!1,c.drawImage(n,0,0);let h=Date.now();const m=i(this,w,g).call(this,c.getImageData(0,0,s,o),e);let d=0;for(const[t,e]of m)0!=t&&(d+=e);this.T={total:d,colors:m},h=Date.now();const b=new OffscreenCanvas(3,3),p=b.getContext("2d");p.clearRect(0,0,3,3),p.fillStyle="white",p.fillRect(1,1,1,1);for(let t=this.coords[3];t{const[n,s,o,a]=e.split(",").map(Number);(s>>24==0?0:s.get(e)??-2;const a=o.get(n);o.set(n,a?a+1:1)}return o};var $,T,S,k,D=class{constructor(t,i){e(this,y),this.name=t,this.version=i,this.O=null,this.B="bm-l",this.I=null,this.H=null,this.A=[]}W(t){this.O=t}P(){return this.A.length>0&&(this.H=this.A.pop()),this}_(t){t?.appendChild(this.I),this.I=null,this.H=null,this.A=[]}U(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"div",{},t)),this}G(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"p",{},t)),this}F(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"small",{},t)),this}R(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"span",{},t)),this}j(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"details",{},t)),this}V(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"summary",{},t)),this}Y(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"img",{},t)),this}q(t,e={},n=()=>{}){return n(this,i(this,y,x).call(this,"h"+t,{},e)),this}J(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"hr",{},t)),this}X(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"br",{},t)),this}Z(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"form",{},t)),this}K(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"fieldset",{},t)),this}tt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"legend",{},t)),this}et(t={},e=()=>{}){const n=i(this,y,x).call(this,"label",{textContent:t.textContent??""});delete t.textContent;const s=i(this,y,x).call(this,"input",{type:"checkbox"},t);return n.insertBefore(s,n.firstChild),this.P(),e(this,n,s),this}it(t={},e=()=>{}){const n=i(this,y,x).call(this,"label",{textContent:t.textContent??"",for:t.id??""});return delete t.textContent,this.P(),e(this,n,i(this,y,x).call(this,"select",{},t)),this}nt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"option",{},t)),this}st(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"ol",{},t)),this}ot(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"ul",{},t)),this}rt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"menu",{},t)),this}lt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"li",{},t)),this}ct(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"table",{},t)),this}ht(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"caption",{},t)),this}dt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"thead",{},t)),this}ut(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"tbody",{},t)),this}bt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"tfoot",{},t)),this}ft(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"tr",{},t)),this}wt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"th",{},t)),this}gt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"td",{},t)),this}yt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"button",{},t)),this}xt(t={},e=()=>{}){const n=t.title??t.textContent??"Help: No info";delete t.textContent,t.title=`Help: ${n}`;const s={textContent:"?",className:"bm-R",onclick:()=>{this.vt(this.B,n)}};return e(this,i(this,y,x).call(this,"button",s,t)),this}Mt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"input",{},t)),this}Ct(t={},e=()=>{}){const n=t.textContent??"";delete t.textContent;const s=i(this,y,x).call(this,"div"),o=i(this,y,x).call(this,"input",{type:"file",tabindex:"-1","aria-hidden":"true"},t);this.P();const a=i(this,y,x).call(this,"button",{textContent:n});return this.P(),this.P(),a.addEventListener("click",()=>{o.click()}),o.addEventListener("change",()=>{a.style.maxWidth=`${a.offsetWidth}px`,o.files.length>0?a.textContent=o.files[0].name:a.textContent=n}),e(this,s,o,a),this}$t(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"textarea",{},t)),this}Tt(t={},e=()=>{}){return e(this,i(this,y,x).call(this,"div",{class:"bm-L"},t)),this}St(t=Date.now(),e=500,n={},s=()=>{}){const o="bm-P",a=n?.id||o+"-"+crypto.randomUUID().slice(0,8),r={class:o},l=i(this,y,x).call(this,"time",r,n);return l.id=a,l.dataset.endDate=t,setInterval(()=>{if(!l.isConnected)return;const t=Math.max(l.dataset.endDate-Date.now(),0),e=Math.floor(t/1e3),i=Math.floor(e/3600),n=Math.floor(e%60),s=Math.floor(e%3600/60);l.setAttribute("datetime",`PT${i}H${s}M${n}S`),l.textContent=String(i).padStart(2,"0")+":"+String(s).padStart(2,"0")+":"+String(n).padStart(2,"0")},e),s(this,l),this}vt(t,e,i=!1){const n=document.getElementById(t.replace(/^#/,""));n&&(n instanceof HTMLInputElement?n.value=e:i?n.textContent=e:n.innerHTML=e)}kt(t){if(t.disabled)return;t.disabled=!0,t.style.textDecoration="none";const e=t.closest(".bm-N"),i=t.closest(".bm-L"),n=e.querySelector("h1"),s=e.querySelector(".bm-h");if(e.parentElement.append(e),"expanded"==t.dataset.buttonStatus){s.style.height=s.scrollHeight+"px",e.style.width=e.scrollWidth+"px",s.style.height="0",s.addEventListener("transitionend",function e(){s.style.display="none",t.disabled=!1,t.style.textDecoration="",s.removeEventListener("transitionend",e)});const i=n.cloneNode(!0),o=i.textContent;t.nextElementSibling.appendChild(i),t.textContent="▶",t.dataset.buttonStatus="collapsed",t.ariaLabel=`Unminimize window "${o}"`}else{const n=i.querySelector("h1"),o=n.textContent;n.remove(),s.style.display="",s.style.height="0",e.style.width="",s.style.height=s.scrollHeight+"px",s.addEventListener("transitionend",function e(){s.style.height="",t.disabled=!1,t.style.textDecoration="",s.removeEventListener("transitionend",e)}),t.textContent="▼",t.dataset.buttonStatus="expanded",t.ariaLabel=`Minimize window "${o}"`}}Dt(t,e){const i=document.querySelector(t),n=document.querySelector(e);if(!i||!n)return void this.Lt(`Can not drag! ${i?"":"moveMe"} ${i||n?"":"and "}${n?"":"iMoveThings "}was not found!`);let s,o=!1,a=0,r=null,l=0,c=0,h=0,m=0,d=null;const u=()=>{if(o){const t=Math.abs(l-h),e=Math.abs(c-m);(t>.5||e>.5)&&(l=h,c=m,i.style.transform=`translate(${l}px, ${c}px)`,i.style.left="0px",i.style.top="0px",i.style.right=""),r=requestAnimationFrame(u)}},b=(t,e)=>{o=!0,d=i.getBoundingClientRect(),s=t-d.left,a=e-d.top;const b=window.getComputedStyle(i).transform;if(b&&"none"!==b){const t=new DOMMatrix(b);l=t.m41,c=t.m42}else l=d.left,c=d.top;h=l,m=c,document.body.style.userSelect="none",n.classList.add("bm-F"),document.addEventListener("mousemove",f),document.addEventListener("touchmove",w,{passive:!1}),document.addEventListener("mouseup",p),document.addEventListener("touchend",p),document.addEventListener("touchcancel",p),r&&cancelAnimationFrame(r),u()},p=()=>{o=!1,r&&(cancelAnimationFrame(r),r=null),document.body.style.userSelect="",n.classList.remove("bm-F"),document.removeEventListener("mousemove",f),document.removeEventListener("touchmove",w),document.removeEventListener("mouseup",p),document.removeEventListener("touchend",p),document.removeEventListener("touchcancel",p)},f=t=>{o&&d&&(h=t.clientX-s,m=t.clientY-a)},w=t=>{if(o&&d){const e=t.touches[0];if(!e)return;h=e.clientX-s,m=e.clientY-a,t.preventDefault()}};n.addEventListener("mousedown",function(t){t.preventDefault(),b(t.clientX,t.clientY)}),n.addEventListener("touchstart",function(t){const e=t?.touches?.[0];e&&(b(e.clientX,e.clientY),t.preventDefault())},{passive:!1})}Nt(t){(0,console.info)(`${this.name}: ${t}`),this.vt(this.B,"Status: "+t,!0)}Lt(t){(0,console.error)(`${this.name}: ${t}`),this.vt(this.B,"Error: "+t,!0)}};y=new WeakSet,x=function(t,e={},n={}){const s=document.createElement(t);this.I?(this.H?.appendChild(s),this.A.push(this.H),this.H=s):(this.I=s,this.H=s);for(const[t,n]of Object.entries(e))i(this,y,v).call(this,s,t,n);for(const[t,e]of Object.entries(n))i(this,y,v).call(this,s,t,e);return s},v=function(t,e,i){if("class"==e)t.classList.add(...i.split(/\s+/));else if("for"==e)t.htmlFor=i;else if("tabindex"==e)t.tabIndex=Number(i);else if("readonly"==e)t.readOnly="true"==i||"1"==i;else if("maxlength"==e)t.maxLength=Number(i);else if(e.startsWith("data"))t.dataset[e.slice(5).split("-").map((t,e)=>0==e?t:t[0].toUpperCase()+t.slice(1)).join("")]=i;else if(e.startsWith("aria")){const n=e.slice(5).split("-").map((t,e)=>0==e?t:t[0].toUpperCase()+t.slice(1)).join("");t["aria"+n[0].toUpperCase()+n.slice(1)]=i}else t[e]=i};var L=class extends D{constructor(t,i,n,s=void 0){super(t,i),e(this,$),this.window=null,this.Ot="bm-m",this.Bt=document.body,this.It=JSON.parse(GM_getValue("bmTemplates","{}")),this.scriptVersion=this.It?.scriptVersion,this.schemaVersion=this.It?.schemaVersion,this.Ht=void 0,this.At=n,this.Wt=s}Pt(){if(document.querySelector(`#${this.Ot}`))return void document.querySelector(`#${this.Ot}`).remove();let t="";document.querySelector("#bm-w")||(t=t.concat("z-index: 9001;").trim()),this.window=this.U({id:this.Ot,class:"bm-N",style:t},(t,e)=>{}).Tt().yt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Template Wizard"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.kt(e),e.ontouchend=()=>{e.click()}}).P().U().P().yt({class:"bm-n",textContent:"✖","aria-label":'Close window "Template Wizard"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.Ot}`)?.remove()},e.ontouchend=()=>{e.click()}}).P().P().U({class:"bm-h"}).U({class:"bm-E bm-d"}).q(1,{textContent:"Template Wizard"}).P().P().J().P().U({class:"bm-E"}).q(2,{textContent:"Status"}).P().G({id:"bm-o",textContent:"Loading template storage status..."}).P().P().U({class:"bm-E bm-A"}).q(2,{textContent:"Detected templates:"}).P().P().P().P()._(this.Bt),this.Dt(`#${this.Ot}.bm-N`,`#${this.Ot} .bm-L`),i(this,$,T).call(this),i(this,$,S).call(this)}};$=new WeakSet,T=function(){const t=this.schemaVersion.split(/[-\.\+]/),e=this.At.split(/[-\.\+]/);let n="";t[0]==e[0]?t[1]==e[1]?(n='Template storage health: Healthy!
No futher action required. (Reason: Semantic version matches)',this.Ht="Good"):(n='Template storage health: Poor!
You can still use your template, but some features may not work. It is recommended that you update Blue Marble\'s template storage. (Reason: MINOR version mismatch)',this.Ht="Poor"):t[0]Bad!
It is guaranteed that some features are broken. You might still be able to use the template. It is HIGHLY recommended that you download all templates and update Blue Marble\'s template storage before continuing. (Reason: MAJOR version mismatch)',this.Ht="Bad"):(n='Template storage health: Dead!
Blue Marble can not load the template storage. (Reason: MAJOR version unknown)',this.Ht="Dead");const s=`
If you want to continue using your current templates, then make sure the template storage (schema) is up-to-date.
If you don't want to update the template storage, then downgrade Blue Marble to version ${r(this.scriptVersion)} to continue using your templates.
Alternatively, if you don't care about corrupting the templates listed below, you can fix any issues with the template storage by uploading a new template.`,o=function(){const t=[...document.querySelectorAll("body > div > .hidden")].filter(t=>/version:/i.test(t.textContent));if(t[0]){const e=t[0].textContent?.match(/\d+/);return e?new Date(Number(e[0])):void 0}}();let l=o?a(o):"???";this.vt("#bm-o",`${n}
Your templates were created during Blue Marble version ${r(this.scriptVersion)} with schema version ${r(this.schemaVersion)}.
The current Blue Marble version is ${r(this.version)} and requires schema version ${r(this.At)}.
Wplace was last updated on ${l}.${"Good"!=this.Ht?s:""}`);const c=new D(this.name,this.version);"Dead"!=this.Ht&&(c.U({class:"bm-E bm-x bm-d",style:"gap: 1.5ch;"}),c.yt({textContent:"Download all templates"},(t,e)=>{e.onclick=()=>{e.disabled=!0,this.Wt._t().then(()=>{e.disabled=!1})}}).P()),"Poor"!=this.Ht&&"Bad"!=this.Ht||c.yt({textContent:`Update template storage to ${this.At}`},(t,e)=>{e.onclick=()=>{e.disabled=!0,i(this,$,k).call(this,!0)}}).P(),c.P()._(document.querySelector("#bm-o").parentNode)},S=function(){const t=this.It?.templates;if(Object.keys(t).length>0){const e=document.querySelector(`#${this.Ot} .bm-A`),i=new D(this.name,this.version);i.U({id:"bm-r",class:"bm-E"});for(const e in t){const n=e,o=t[e];if(t.hasOwnProperty(e)){const t=n.split(" "),e=Number(t?.[0]),a=d(t?.[1]||"0",this.Wt.zt),r=o.name||`Template ${e||""}`,l=o?.coords?.split(",").map(Number),c=o.pixels?.total??void 0,h=void 0,m="number"==typeof e?s(e):"???",u="number"==typeof a?s(a):"???",b="number"==typeof c?s(c):"???";i.U({class:"bm-E bm-x"}).U({class:"bm-x",style:"flex-direction: column; gap: 0;"}).U({class:"bm-1",textContent:h||"🖼️"}).P().F({textContent:`#${m}`}).P().P().U({class:"bm-x bm-0"}).q(3,{textContent:r}).P().R({textContent:`Uploaded by user #${u}`}).P().R({textContent:`Coordinates: ${l.join(", ")}`}).P().R({textContent:`Total Pixels: ${b}`}).P().P().P()}}i.P()._(e)}},k=async function(t){if(t){const t=document.querySelector(`#${this.Ot} .bm-h`);t.innerHTML="",new D(this.name,this.version).U({class:"bm-E"}).U({class:"bm-E bm-d"}).q(1,{textContent:"Template Wizard"}).P().P().J().P().U({class:"bm-E"}).q(2,{textContent:"Status"}).P().G({textContent:"Updating template storage. Please wait..."}).P().P().P()._(t)}GM_deleteValue("bmCoords");const e=this.It?.templates;if(Object.keys(e).length>0)for(const[t,i]of Object.entries(e))if(e.hasOwnProperty(t)){const t=new C({displayName:i.name,M:i.tiles});t.L();const e=await this.Wt.Ut(t);await this.Wt.Gt(e,t.displayName,t.coords)}t&&(document.querySelector(`#${this.Ot}`).remove(),new L(this.name,this.version,this.At,this.Wt).Pt())};var N,O,B,I,H=L;N=new WeakSet,O=async function(){GM.setValue("bmTemplates",JSON.stringify(this.Ft))},B=async function(t){const e=t.templates,i=t?.schemaVersion,n=i.split(/[-\.\+]/),s=this.schemaVersion.split(/[-\.\+]/),o=t?.scriptVersion;n[0]==s[0]?(n[1]!=s[1]&&new H(this.name,this.version,this.schemaVersion,this).Pt(),this.Rt=await async function({$:t,jt:i,Rt:n}){if(Object.keys(e).length>0)for(const s in e){const o=s,a=e[s];if(e.hasOwnProperty(s)){const e=o.split(" "),s=Number(e?.[0]),r=e?.[1]||"0",l=a.name||`Template ${s||""}`,c={total:a.pixels?.total,colors:new Map(Object.entries(a.pixels?.colors||{}).map(([t,e])=>[Number(t),e]))},h=a.tiles,m={},d={},u=t*i;for(const t in h)if(h.hasOwnProperty(t)){const e=b(h[t]),i=new Blob([e],{type:"image/png"}),n=await createImageBitmap(i);m[t]=n;const s=new OffscreenCanvas(u,u).getContext("2d");s.drawImage(n,0,0);const o=s.getImageData(0,0,n.width,n.height);d[t]=new Uint32Array(o.data.buffer)}const p=new C({displayName:l,p:s||this.Rt?.length||0,v:r||""});p.T=c,p.M=m,p.C=d,n.push(p)}}return n}({$:this.$,jt:this.jt,Rt:this.Rt})):n[0]>>24&255,w=b>>>24&255,g=m.get(p)??-2;if(this.Xt.get(g)&&(e[i*r+l]=b),-1==g){const t=536870912;this.Xt.get(g)?e[i*r+l]=0:(h/n&1)==(u/n&1)?(e[i*r+l]=t,e[(i-1)*r+(l-1)]=t,e[(i-1)*r+(l+1)]=t,e[(i+1)*r+(l-1)]=t,e[(i+1)*r+(l+1)]=t):(e[i*r+l]=0,e[(i-1)*r+l]=t,e[(i+1)*r+l]=t,e[i*r+(l-1)]=t,e[i*r+(l+1)]=t)}if(-1==g&&b<=c){const t=d.get(g);d.set(g,t?t+1:1);continue}if(f<=c||w<=c)continue;if((m.get(b)??-2)!=g)continue;const y=d.get(g);d.set(g,y?y+1:1)}return{Qt:d,Zt:e}};var A=class{constructor(){this.Kt=Math.ceil(80/1300*window.innerWidth),this.te=M.slice(1)}ee(t){const e=document.createElement("div");for(let t=0;t{t.parentNode.childElementCount<=1?t.parentNode.remove():t.remove()},e.appendChild(t)}t.appendChild(e)}},W=class extends HTMLElement{};customElements.define("confetti-piece",W);var P,_,z,U,G,F,R,j,E,V,Y=class extends D{constructor(t,e){super(t,e),this.window=null,this.Ot="bm-i",this.Bt=document.body}Pt(){document.querySelector(`#${this.Ot}`)?document.querySelector(`#${this.Ot}`).remove():(this.window=this.U({id:this.Ot,class:"bm-N"},(t,e)=>{}).Tt().yt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Credits"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.kt(e),e.ontouchend=()=>{e.click()}}).P().U().P().yt({class:"bm-n",textContent:"✖","aria-label":'Close window "Credits"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.Ot}`)?.remove()},e.ontouchend=()=>{e.click()}}).P().P().U({class:"bm-h"}).U({class:"bm-E bm-d"}).q(1,{textContent:"Credits"}).P().P().J().P().U({class:"bm-E bm-A"}).R({role:"img","aria-label":this.name}).R({innerHTML:"\n██████╗ ██╗ ██╗ ██╗███████╗\n██╔══██╗██║ ██║ ██║██╔════╝\n██████╔╝██║ ██║ ██║█████╗ \n██╔══██╗██║ ██║ ██║██╔══╝ \n██████╔╝███████╗╚██████╔╝███████╗\n╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝\n\n███╗ ███╗ █████╗ ██████╗ ██████╗ ██╗ ███████╗\n████╗ ████║██╔══██╗██╔══██╗██╔══██╗██║ ██╔════╝\n██╔████╔██║███████║██████╔╝██████╔╝██║ █████╗ \n██║╚██╔╝██║██╔══██║██╔══██╗██╔══██╗██║ ██╔══╝ \n██║ ╚═╝ ██║██║ ██║██║ ██║██████╔╝███████╗███████╗\n╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚══════╝\n",class:"bm-Q","aria-hidden":"true"}).P().P().X().P().J().P().X().P().R({textContent:'"Blue Marble" userscript is made by SwingTheVine.'}).P().X().P().R({innerHTML:'The Blue Marble Website is made by crqch.'}).P().X().P().R({textContent:`The Blue Marble Website used until ${a(new Date(175606932e4))} was made by Camille Daguin.`}).P().X().P().R({textContent:'The favicon "Blue Marble" is owned by NASA. (The image of the Earth is owned by NASA)'}).P().X().P().R({textContent:"Special Thanks:"}).P().ot().lt({textContent:"Espresso, Meqa, and Robot for moderating SwingTheVine's community."}).P().lt({innerHTML:'nof, darkness for creating similar userscripts!'}).P().lt({innerHTML:'Wonda for the Blue Marble banner image!'}).P().lt({innerHTML:'BullStein, allanf181 for being early beta testers!'}).P().lt({innerHTML:'guidu_ and Nick-machado for the original "Minimize" Button code!'}).P().lt({innerHTML:'Nomad and Gustav for the tutorials!'}).P().lt({innerHTML:'cfp for creating the template overlay that Blue Marble was based on!'}).P().lt({innerHTML:'Force Network for hosting the telemetry server!'}).P().lt({innerHTML:'TheBlueCorner for getting me interested in online pixel canvases!'}).P().P().X().P().R({innerHTML:'Donators:'}).P().ot().lt({textContent:"Espresso"}).P().lt({textContent:"BEST FAN"}).P().lt({textContent:"Jack"}).P().lt({textContent:"raiken_au"}).P().lt({textContent:"Jacob"}).P().lt({textContent:"StupidOne"}).P().lt({textContent:"1 Anonymous Supporter"}).P().P().P().P().P()._(this.Bt),this.Dt(`#${this.Ot}.bm-N`,`#${this.Ot} .bm-L`))}},q=class extends D{constructor(t){super(t.name,t.version),e(this,P),this.window=null,this.Ot="bm-p",this.ie="bm-y",this.Bt=document.body,this.Wt=t.O?.Wt,this.ne='',this.se='';const{palette:i,N:n}=this.Wt.Jt;this.palette=i,this.oe=0,this.ae=0,this.re=new Map,this.le=new Map,this.ce=0,this.he=0,this.timeRemaining=0,this.me="",this.sortPrimary="id",this.sortSecondary="ascending",this.showUnused=!1}Pt(){if(document.querySelector(`#${this.Ot}`))return void document.querySelector(`#${this.Ot}`).remove();this.window=this.U({id:this.Ot,class:"bm-N"},(t,e)=>{}).Tt().yt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Color Filter"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.kt(e),e.ontouchend=()=>{e.click()}}).P().U().P().U({class:"bm-x"}).yt({class:"bm-n",textContent:"🗗","aria-label":'Switch to windowed mode for "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.Ot}`)?.remove(),this.de()},e.ontouchend=()=>{e.click()}}).P().yt({class:"bm-n",textContent:"✖","aria-label":'Close window "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.Ot}`)?.remove()},e.ontouchend=()=>{e.click()}}).P().P().P().U({class:"bm-h"}).U({class:"bm-E bm-d"}).q(1,{textContent:"Color Filter"}).P().P().J().P().U({class:"bm-E bm-s bm-d",style:"gap: 1.5ch;"}).yt({textContent:"Hide All Colors"},(t,e)=>{e.onclick=()=>i(this,P,U).call(this,!1)}).P().yt({textContent:"Refresh Data"},(t,e)=>{e.onclick=()=>{e.disabled=!0,this.ue(),e.disabled=!1}}).P().yt({textContent:"Show All Colors"},(t,e)=>{e.onclick=()=>i(this,P,U).call(this,!0)}).P().P().U({class:"bm-E bm-A"}).U({class:"bm-E",style:"margin-left: 2.5ch; margin-right: 2.5ch;"}).U({class:"bm-E"}).R({id:"bm-e",innerHTML:"Tiles Loaded: 0 / ???"}).P().X().P().R({id:"bm-9",innerHTML:"Correct Pixels: ???"}).P().X().P().R({id:"bm-f",innerHTML:"Total Pixels: ???"}).P().X().P().R({id:"bm-4",innerHTML:"Complete: ??? (???)"}).P().X().P().R({id:"bm-5",innerHTML:"??? ???"}).P().P().U({class:"bm-E"}).G({innerHTML:`Colors with the icon ${this.ne.replace("{e.onclick=t=>{t.preventDefault();const e=new FormData(document.querySelector(`#${this.Ot} form`)),n={};for(const[t,i]of e)n[t]=i;i(this,P,z).call(this,n.sortPrimary,n.sortSecondary,"on"==n.showUnused)}}).P().P().P().P().P().P().P()._(this.Bt),this.Dt(`#${this.Ot}.bm-N`,`#${this.Ot} .bm-L`);const t=document.querySelector(`#${this.Ot} .bm-E.bm-A`);i(this,P,_).call(this,t),i(this,P,z).call(this,this.sortPrimary,this.sortSecondary,this.showUnused),this.vt("#bm-e",`Tiles Loaded: ${s(this.oe)} / ${s(this.ae)}`),this.vt("#bm-9",`Correct Pixels: ${s(this.ce)}`),this.vt("#bm-f",`Total Pixels: ${s(this.he)}`),this.vt("#bm-4",`Remaining: ${s((this.he||0)-(this.ce||0))} (${o(((this.he||0)-(this.ce||0))/(this.he||1))})`),this.vt("#bm-5",`Completed at: `)}de(){if(document.querySelector(`#${this.Ot}`))return void document.querySelector(`#${this.Ot}`).remove();this.window=this.U({id:this.Ot,class:"bm-N bm-G"}).Tt().yt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Color Filter"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.kt(e),e.ontouchend=()=>{e.click()}}).P().U().P().U({class:"bm-x"}).yt({class:"bm-n",textContent:"🗖","aria-label":'Switch to fullscreen mode for "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.Ot}`)?.remove(),this.Pt()},e.ontouchend=()=>{e.click()}}).P().yt({class:"bm-n",textContent:"✖","aria-label":'Close window "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.Ot}`)?.remove()},e.ontouchend=()=>{e.click()}}).P().P().P().U({class:"bm-h"}).U({class:"bm-E bm-d"}).q(1,{textContent:"Color Filter"}).P().P().J().P().U({class:"bm-E bm-s bm-d",style:"gap: 1.5ch;"}).yt({textContent:"None"},(t,e)=>{e.onclick=()=>i(this,P,U).call(this,!1)}).P().yt({textContent:"Refresh"},(t,e)=>{e.onclick=()=>{e.disabled=!0,this.ue(),e.disabled=!1}}).P().yt({textContent:"All"},(t,e)=>{e.onclick=()=>i(this,P,U).call(this,!0)}).P().P().U({class:"bm-E bm-A"}).P().P().P()._(this.Bt),this.Dt(`#${this.Ot}.bm-N`,`#${this.Ot} .bm-L`);const t=document.querySelector(`#${this.Ot} .bm-E.bm-A`);i(this,P,_).call(this,t),i(this,P,z).call(this,this.sortPrimary,this.sortSecondary,this.showUnused)}ue(){i(this,P,G).call(this);const t=document.querySelector(`#${this.ie}`),e={};for(const t of this.palette){const i=this.re.get(t.id)??0,n=s(i);let a=0,r="0",l=o(1);0!=i&&(a=this.le.get(t.id)??"???","number"!=typeof a&&this.oe==this.ae&&t.id&&(a=0),r="string"==typeof a?a:s(a),l=isNaN(a/i)?"???":o(a/i));const c=parseInt(i)-parseInt(a);e[t.id]={be:i,pe:n,fe:a,we:r,ge:l,ye:c}}if(!t)return e;const n=Array.from(t.children);for(const t of n){const i=parseInt(t.dataset.id),{fe:n,we:s,ge:o,be:a,pe:r,ye:l}=e[i];t.dataset.correct=Number.isNaN(parseInt(n))?"0":n,t.dataset.total=a,t.dataset.percent="%"==o.slice(-1)?o.slice(0,-1):"0",t.dataset.incorrect=l||0;const c=document.querySelector(`#${this.Ot} .bm-t[data-id="${i}"] .bm-6`);c&&(c.textContent=`${s} / ${r}`);const h=document.querySelector(`#${this.Ot} .bm-t[data-id="${i}"] .bm-3`);h&&(h.textContent=`${"number"!=typeof l||isNaN(l)?"???":l} incorrect pixels. Completed: ${o}`)}i(this,P,z).call(this,this.sortPrimary,this.sortSecondary,this.showUnused)}};P=new WeakSet,_=function(t){const e=t.closest(`#${this.Ot}`)?.classList.contains("bm-G"),i=new D(this.name,this.version);i.U({id:this.ie});const n=this.ue();for(const t of this.palette){const s="#"+f(t.rgb).toUpperCase(),o=p(t.rgb);let a=1.05/(o+.05)>(o+.05)/.05?"white":"black";t.id||(a="transparent");const r="white"==a?"bm-b":"bm-c",{fe:l,we:c,ge:h,be:m,pe:d,ye:u}=n[t.id],b=!!this.Wt.Xt.get(t.id);if(e){const e=`background-size: auto 100%; background-repeat: repeat-x; background-image: url("data:image/svg+xml;utf8,");`;i.U({class:"bm-E bm-t bm-s","data-id":t.id,"data-name":t.name,"data-premium":+t.premium,"data-correct":Number.isNaN(parseInt(l))?"0":l,"data-total":m,"data-percent":"%"==h.slice(-1)?h.slice(0,-1):"0","data-incorrect":u||0}).U({class:"bm-7",style:`background-color: rgb(${t.rgb?.map(t=>Number(t)||0).join(",")});${t.premium?e:""}`}).yt({class:"bm-u "+r,"data-state":b?"hidden":"shown","aria-label":b?`Show the color ${t.name||""} on templates.`:`Hide the color ${t.name||""} on templates.`,innerHTML:b?this.se.replace("{i.onclick=()=>{i.style.textDecoration="none",i.disabled=!0,"shown"==i.dataset.state?(i.innerHTML=this.se.replace("Number(t)||0).join(",")});`}).yt({class:"bm-u "+r,"data-state":b?"hidden":"shown","aria-label":b?`Show the color ${t.name||""} on templates.`:`Hide the color ${t.name||""} on templates.`,innerHTML:b?this.se.replace("{i.onclick=()=>{i.style.textDecoration="none",i.disabled=!0,"shown"==i.dataset.state?(i.innerHTML=this.se.replace("{const o=n.getAttribute("data-"+t),a=s.getAttribute("data-"+t),r=parseFloat(o),l=parseFloat(a),c=!isNaN(r),h=!isNaN(l);if(i?n.classList.remove("bm-B"):Number(n.getAttribute("data-total"))||n.classList.add("bm-B"),c&&h)return"ascending"===e?r-l:l-r;{const t=o.toLowerCase(),i=a.toLowerCase();return ti?"ascending"===e?1:-1:0}}),s.forEach(t=>n.appendChild(t))},U=function(t){const e=document.querySelector(`#${this.ie}`),i=Array.from(e.children);for(const e of i){if(e.classList?.contains("bm-B"))continue;const i=e.querySelector(".bm-7 button");("hidden"!=i.dataset.state||t)&&("shown"==i.dataset.state&&t||i.click())}},G=function(){this.he=0,this.ce=0,this.le=new Map,this.re=new Map;for(const t of this.Wt.Rt){const e=t.T?.total??0;this.he+=e??0;const i=t.T?.colors??new Map;for(const[t,e]of i){const i=Number(e)||0,n=this.re.get(t)??0;this.re.set(t,n+i)}const n=t.T?.correct??{};this.oe+=Object.keys(n).length,this.ae+=Object.keys(t.M).length;for(const t of Object.values(n))for(const[e,i]of t){const t=Number(i)||0;this.ce+=t;const n=this.le.get(e)??0;this.le.set(e,n+t)}}this.ce>=this.he&&this.he&&this.oe==this.ae&&(new A).ee(document.querySelector(`#${this.Ot}`)),this.timeRemaining=new Date(30*(this.he-this.ce)*1e3+Date.now()),this.me=a(this.timeRemaining)},F=new WeakSet,R=function(){new q(this).Pt()},j=async function(t,e,i){i.preventDefault();const n=await async function(t){let e="";return t&&(e=t.clipboardData.getData("text/plain")),0!=e.length||(await navigator.clipboard.readText().then(t=>{e=t}).catch(t=>{l("Failed to retrieve clipboard data using navigator! Using fallback methods...")}),0!=e.length||(e=window.clipboardData?.getData("Text"))),e}(i),s=n.split(/[^a-zA-Z0-9]+/).filter(t=>t).map(Number).filter(t=>!isNaN(t));2==s.length&&"bm-H"==e.id?(t.vt("bm-H",s?.[0]||""),t.vt("bm-I",s?.[1]||"")):1==s.length?t.vt(e.id,s?.[0]||""):(t.vt("bm-J",s?.[0]||""),t.vt("bm-K",s?.[1]||""),t.vt("bm-H",s?.[2]||""),t.vt("bm-I",s?.[3]||""))},E=new WeakSet,V=function(t){const e=JSON.parse(GM_getValue("bmUserSettings","{}"));e.telemetry=t,GM.setValue("bmUserSettings",JSON.stringify(e))};var J=GM_info.script.name.toString(),X=GM_info.script.version.toString();!function(t){const e=document.createElement("script");e.setAttribute("bm-S",J),e.setAttribute("bm-O","color: cornflowerblue;"),e.textContent=`(${t})();`,document.documentElement?.appendChild(e),e.remove()}(()=>{const t=document.currentScript,e=t?.getAttribute("bm-S")||"Blue Marble",i=t?.getAttribute("bm-O")||"",n=new Map;window.addEventListener("message",t=>{const{source:s,endpoint:o,blobID:a,blobData:r,blink:l}=t.data;if(Date.now(),"blue-marble"==s&&a&&r&&!o){const t=n.get(a);"function"==typeof t?t(r):h(`%c${e}%c: Attempted to retrieve a blob (%s) from queue, but the blobID was not a function! Skipping...`,i,"",a),n.delete(a)}});const s=window.fetch;window.fetch=async function(...t){const e=await s.apply(this,t),i=e.clone(),o=(t[0]instanceof Request?t[0]?.url:t[0])||"ignore",a=i.headers.get("content-type")||"";if(a.includes("application/json"))i.json().then(t=>{window.postMessage({source:"blue-marble",endpoint:o,jsonData:t},"*")}).catch(t=>{});else if(a.includes("image/")&&!o.includes("openfreemap")&&!o.includes("maps")){const t=Date.now(),e=await i.blob();return new Promise(s=>{const a=crypto.randomUUID();n.set(a,t=>{s(new Response(t,{headers:i.headers,status:i.status,statusText:i.statusText}))}),window.postMessage({source:"blue-marble",endpoint:o,blobID:a,blobData:e,blink:t})}).catch(t=>{Date.now()})}return e}});var Q=GM_getResourceText("CSS-BM-File");GM_addStyle(Q);var Z,K="robotoMonoInjectionPoint";K.indexOf("@font-face")+1?GM_addStyle(K):((Z=document.createElement("link")).href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap",Z.rel="preload",Z.as="style",Z.onload=function(){this.onload=null,this.rel="stylesheet"},document.head?.appendChild(Z)),new class{constructor(){this.xe=null,this.ve=null,this.Me="#bm-j"}Ce(t){return this.ve=t,this.xe=new MutationObserver(t=>{for(const e of t)for(const t of e.addedNodes)t instanceof HTMLElement&&t.matches?.(this.Me)}),this}$e(){return this.xe}observe(t,e=!1,i=!1){t.observe(this.ve,{childList:e,subtree:i})}};var tt=new class extends D{constructor(t,i){super(t,i),e(this,F),this.window=null,this.Ot="bm-w",this.Bt=document.body}Pt(){document.querySelector(`#${this.Ot}`)?this.Lt("Main window already exists!"):(this.window=this.U({id:this.Ot,class:"bm-N bm-G",style:"top: 10px; left: unset; right: 75px;"},(t,e)=>{}).Tt().yt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Blue Marble"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.kt(e),e.ontouchend=()=>{e.click()}}).P().U().P().P().U({class:"bm-h"}).U({class:"bm-E"}).Y({class:"bm-M",src:"https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png"},(t,e)=>{const i=new Date;204==Math.floor((i.getTime()-new Date(i.getFullYear(),0,1))/864e5)+1&&(e.parentNode.style.position="relative",e.parentNode.innerHTML=e.parentNode.innerHTML+'',e.onload=()=>{(new A).ee(document.querySelector(`#${this.Ot}`))})}).P().q(1,{textContent:this.name}).P().P().J().P().U({class:"bm-E"}).R({id:"bm-q",textContent:"Droplets:"}).P().X().P().R({id:"bm-k",textContent:"Next level in..."}).P().X().P().R({textContent:"Charges: "}).St(Date.now(),1e3,{style:"font-weight: 700;"},(t,e)=>{t.O.Te=e.id}).P().P().P().J().P().U({class:"bm-E"}).U({class:"bm-E"}).yt({class:"bm-n bm-C",style:"margin-top: 0;",innerHTML:''},(t,e)=>{e.onclick=()=>{const e=t.O?.Se;e?.[0]?(t.vt("bm-J",e?.[0]||""),t.vt("bm-K",e?.[1]||""),t.vt("bm-H",e?.[2]||""),t.vt("bm-I",e?.[3]||"")):t.Lt("Coordinates are malformed! Did you try clicking on the canvas first?")}}).P().Mt({type:"number",id:"bm-J",class:"bm-v",placeholder:"Tl X",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,F,j).call(this,t,e,n))}).P().Mt({type:"number",id:"bm-K",class:"bm-v",placeholder:"Tl Y",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,F,j).call(this,t,e,n))}).P().Mt({type:"number",id:"bm-H",class:"bm-v",placeholder:"Px X",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,F,j).call(this,t,e,n))}).P().Mt({type:"number",id:"bm-I",class:"bm-v",placeholder:"Px Y",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,F,j).call(this,t,e,n))}).P().P().U({class:"bm-E"}).Ct({class:"bm-D",textContent:"Upload Template",accept:"image/png, image/jpeg, image/webp, image/bmp, image/gif"}).P().P().U({class:"bm-E bm-s"}).yt({textContent:"Disable","data-button-status":"shown"},(t,e)=>{e.onclick=()=>{e.disabled=!0,"shown"==e.dataset.buttonStatus?(t.O?.Wt?.ke(!1),e.dataset.buttonStatus="hidden",e.textContent="Enable",t.Nt("Disabled templates!")):(t.O?.Wt?.ke(!0),e.dataset.buttonStatus="shown",e.textContent="Disable",t.Nt("Enabled templates!")),e.disabled=!1}}).P().yt({textContent:"Create"},(t,e)=>{e.onclick=()=>{const e=document.querySelector(`#${this.Ot} .bm-D`),i=document.querySelector("#bm-J");if(!i.checkValidity())return i.reportValidity(),void t.Lt("Coordinates are malformed! Did you try clicking on the canvas first?");const n=document.querySelector("#bm-K");if(!n.checkValidity())return n.reportValidity(),void t.Lt("Coordinates are malformed! Did you try clicking on the canvas first?");const s=document.querySelector("#bm-H");if(!s.checkValidity())return s.reportValidity(),void t.Lt("Coordinates are malformed! Did you try clicking on the canvas first?");const o=document.querySelector("#bm-I");if(!o.checkValidity())return o.reportValidity(),void t.Lt("Coordinates are malformed! Did you try clicking on the canvas first?");e?.files[0]?(t?.O?.Wt.Gt(e.files[0],e.files[0]?.name.replace(/\.[^/.]+$/,""),[Number(i.value),Number(n.value),Number(s.value),Number(o.value)]),t.Nt("Drew to canvas!")):t.Lt("No file selected!")}}).P().yt({textContent:"Filter"},(t,e)=>{e.onclick=()=>i(this,F,R).call(this)}).P().P().U({class:"bm-E"}).$t({id:this.B,placeholder:`Status: Sleeping...\nVersion: ${this.version}`,readOnly:!0}).P().P().U({class:"bm-E bm-s",style:"margin-bottom: 0; flex-direction: column;"}).U({class:"bm-s"}).yt({class:"bm-n",innerHTML:"🧙",title:"Template Wizard"},(t,e)=>{e.onclick=()=>{const e=t.O?.Wt;new H(this.name,this.version,e?.schemaVersion,e).Pt()}}).P().yt({class:"bm-n",innerHTML:"🎨",title:"Template Color Converter"},(t,e)=>{e.onclick=()=>{window.open("https://pepoafonso.github.io/color_converter_wplace/","_blank","noopener noreferrer")}}).P().yt({class:"bm-n",innerHTML:"🌐",title:"Official Blue Marble Website"},(t,e)=>{e.onclick=()=>{window.open("https://bluemarble.lol/","_blank","noopener noreferrer")}}).P().yt({class:"bm-n",title:"Donate to SwingTheVine",innerHTML:''},(t,e)=>{e.onclick=()=>{window.open("https://ko-fi.com/swingthevine","_blank","noopener noreferrer")}}).P().yt({class:"bm-n",innerHTML:"🤝",title:"Credits"},(t,e)=>{e.onclick=()=>{new Y(this.name,this.version).Pt()}}).P().P().F({textContent:"Made by SwingTheVine",style:"margin-top: auto;"}).P().P().P().P().P()._(this.Bt),this.Dt(`#${this.Ot}.bm-N`,`#${this.Ot} .bm-L`))}}(J,X),et=new class{constructor(t,i,n){e(this,N),this.name=t,this.version=i,this.I=n,this.schemaVersion="2.0.0",this.De=null,this.zt="!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",this.$=1e3,this.jt=3,this.qt=3,this.Jt=function(t){const e=M;e.unshift({id:-1,premium:!1,name:"Erased",rgb:[222,250,206]}),e.unshift({id:-2,premium:!1,name:"Other",rgb:[0,0,0]});const i=new Map;for(const n of e){if(0==n.id||-2==n.id)continue;const e=n.rgb[0],s=n.rgb[1],o=n.rgb[2];for(let a=-t;a<=t;a++)for(let r=-t;r<=t;r++)for(let l=-t;l<=t;l++){const t=e+a,c=s+r,h=o+l;if(t<0||t>255||c<0||c>255||h<0||h>255)continue;const m=(255<<24|h<<16|c<<8|t)>>>0;i.has(m)||i.set(m,n.id)}}return{palette:e,N:i}}(this.qt),this.Vt=null,this.Le="",this.Rt=[],this.Ft=null,this.Ne=!0,this.Oe=null,this.Xt=new Map}async Be(){return{whoami:this.name.replace(" ",""),scriptVersion:this.version,schemaVersion:this.schemaVersion,templates:{}}}async Gt(t,e,n){this.Ft||(this.Ft=await this.Be()),this.I.Nt(`Creating template at ${n.join(", ")}...`);const s=new C({displayName:e,p:0,v:m(this.De||0,this.zt),file:t,coords:n}),{k:o,D:a}=await s.S(this.$,this.Jt);s.M=o;const r={total:s.T.total,colors:Object.fromEntries(s.T.colors)};this.Ft.templates[`${s.p} ${s.v}`]={name:s.displayName,coords:n.join(", "),enabled:!0,pixels:r,tiles:a},this.Rt=[],this.Rt.push(s),this.I.Nt(`Template created at ${n.join(", ")}!`),await i(this,N,O).call(this)}Ie(){}async He(){this.Ft||(this.Ft=await this.Be())}async Ae(){l("Downloading all templates...");for(const t of this.Rt)await this.We(t),await n(500)}async _t(){const t=JSON.parse(GM_getValue("bmTemplates","{}"))?.templates;if(Object.keys(t).length>0)for(const[e,i]of Object.entries(t))t.hasOwnProperty(e)&&(await this.We(new C({displayName:i.name,p:e.split(" ")?.[0],v:e.split(" ")?.[1],M:i.tiles})),await n(500))}async We(t){t.L();const e=`${t.coords.join("-")}_${t.displayName.replaceAll(" ","-")}`,i=await this.Ut(t);await GM.download({url:URL.createObjectURL(i),name:e+".png",Pe:"uniquify",onload:()=>{l(`Download of template '${e}' complete!`)},onerror:(t,i)=>{c(`Download of template '${e}' failed because ${t}! Details: ${i}`)},ontimeout:()=>{h(`Download of template '${e}' has timed out!`)}})}async Ut(t){const e=t.M,i=Object.keys(e).sort(),n=await Promise.all(i.map(t=>{return i=e[t],new Promise((t,e)=>{const n=new Image;n.onload=()=>t(n),n.onerror=e,n.src="data:image/png;base64,"+i});var i}));let s=1/0,o=1/0,a=0,r=0;i.forEach((t,e)=>{const[i,l,c,h]=t.split(",").map(Number),m=n[e],d=i*this.$+c,u=l*this.$+h;s=Math.min(s,d),o=Math.min(o,u),a=Math.max(a,d+m.width/this.jt),r=Math.max(r,u+m.height/this.jt)});const l=a-s,c=r-o,h=l*this.jt,m=c*this.jt,d=new OffscreenCanvas(h,m),u=d.getContext("2d");i.forEach((t,e)=>{const[i,a,r,l]=t.split(",").map(Number),c=n[e],h=i*this.$+r,m=a*this.$+l;u.drawImage(c,(h-s)*this.jt,(m-o)*this.jt,c.width,c.height)}),u.globalCompositeOperation="destination-over",u.drawImage(d,0,-1),u.drawImage(d,0,1),u.drawImage(d,-1,0),u.drawImage(d,1,0);const b=new OffscreenCanvas(l,c),p=b.getContext("2d");return p.imageSmoothingEnabled=!1,p.drawImage(d,0,0,l*this.jt,c*this.jt,0,0,l,c),b.convertToBlob({type:"image/png"})}async _e(t,e){if(!this.Ne)return t;const n=this.$*this.jt;e=e[0].toString().padStart(4,"0")+","+e[1].toString().padStart(4,"0");const o=this.Rt;o.sort((t,e)=>t.p-e.p);const a=o.map(t=>{const i=Object.keys(t.M).filter(t=>t.startsWith(e));if(0===i.length)return null;const n=i.map(e=>{const i=e.split(",");return{ze:t,Ue:t.M[e],C:t.C?.[e],Ge:[i[0],i[1]],Fe:[i[2],i[3]]}});return n?.[0]}).filter(Boolean),r=a?.length||0;if(!(r>0))return this.I.Nt(`Sleeping\nVersion: ${this.version}`),t;{const t=s(o.filter(t=>Object.keys(t.M).filter(t=>t.startsWith(e)).length>0).reduce((t,e)=>t+(e.T.total||0),0));this.I.Nt(`Displaying ${r} template${1==r?"":"s"}.\nTotal pixels: ${t}`)}const l=await createImageBitmap(t),c=new OffscreenCanvas(n,n),h=c.getContext("2d");h.imageSmoothingEnabled=!1,h.beginPath(),h.rect(0,0,n,n),h.clip(),h.clearRect(0,0,n,n),h.drawImage(l,0,0,n,n);const m=h.getImageData(0,0,n,n),d=new Uint32Array(m.data.buffer);for(const t of a){const n=!!t.ze.T?.colors?.get(-1);let s=t.C.slice();const o=Number(t.Fe[0])*this.jt,a=Number(t.Fe[1])*this.jt;if(0!=this.Xt.size||n||h.drawImage(t.Ue,o,a),!s){const e=h.getImageData(o,a,t.Ue.width,t.Ue.height);s=new Uint32Array(e.data.buffer)}Date.now();const{Qt:r,Zt:l}=i(this,N,I).call(this,{Et:d,Vt:s,Yt:[o,a,t.Ue.width,t.Ue.height]});let c=0;const m=0;for(const[t,e]of r)t!=m&&(c+=e);(0!=this.Xt.size||n)&&h.drawImage(await createImageBitmap(new ImageData(new Uint8ClampedArray(l.buffer),t.Ue.width,t.Ue.height)),o,a),void 0===t.ze.T.correct&&(t.ze.T.correct={}),t.ze.T.correct[e]=r}return await c.convertToBlob({type:"image/png"})}Re(t){"BlueMarble"==t?.whoami&&i(this,N,B).call(this,t)}ke(t){this.Ne=t}}(J,X,tt),it=new class{constructor(t){this.Wt=t,this.je=!1,this.Te="",this.Se=[],this.Ee=[]}Ve(t){window.addEventListener("message",async e=>{const i=e.data,n=i.jsonData;if(!i||"blue-marble"!==i.source)return;if(!i.endpoint)return;const o=i.endpoint?.split("?")[0].split("/").filter(t=>t&&isNaN(Number(t))).filter(t=>t&&!t.includes(".")).pop();switch(o){case"me":if(n.status&&"2"!=n.status?.toString()[0])return void t.Lt("You are not logged in or Wplace is offline!\nCould not fetch userdata.");const e=Math.ceil(Math.pow(Math.floor(n.level)*Math.pow(30,.65),1/.65)-n.pixelsPainted);if(n.id||n.id,this.Wt.De=n.id,0!=this.Te.length){const t=document.querySelector("#"+this.Te);if(t){const e=n.charges;t.dataset.endDate=Date.now()+(e.max-e.count)*e.cooldownMs}}t.vt("bm-q",`Droplets: ${s(n.droplets)}`),t.vt("bm-k",`Next level in ${s(e)} pixel${1==e?"":"s"}`);break;case"pixel":const o=i.endpoint.split("?")[0].split("/").filter(t=>t&&!isNaN(Number(t))),l=new URLSearchParams(i.endpoint.split("?")[1]),c=[l.get("x"),l.get("y")];if(this.Se.length&&(!o.length||!c.length))return void t.Lt("Coordinates are malformed!\nDid you try clicking the canvas first?");this.Se=[...o,...c];const h=(a=o,r=c,[parseInt(a[0])%4*1e3+parseInt(r[0]),parseInt(a[1])%4*1e3+parseInt(r[1])]),m=document.querySelectorAll("span");for(const t of m)if(t.textContent.trim().includes(`${h[0]}, ${h[1]}`)){let e=document.querySelector("#bm-j");const i=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${c[0]}, Px Y: ${c[1]})`;e?e.textContent=i:(e=document.createElement("span"),e.id="bm-j",e.textContent=i,e.style="margin-left: calc(var(--spacing)*3); font-size: small;",t.parentNode.parentNode.insertAdjacentElement("afterend",e))}break;case"tile":case"tiles":let d=i.endpoint.split("/");d=[parseInt(d[d.length-2]),parseInt(d[d.length-1].replace(".png",""))];const u=i.blobID,b=i.blobData,p=(Date.now(),await this.Wt._e(b,d));window.postMessage({source:"blue-marble",blobID:u,blobData:p,blink:i.blink});break;case"robots":this.je="false"==n.userscript?.toString().toLowerCase()}var a,r})}async Ye(t){let e=GM_getValue("bmUserSettings","{}");if(e=JSON.parse(e),!e||!e.telemetry||!e.uuid)return;const i=navigator.userAgent;let n=await this.qe(i),s=this.Je(i);GM_xmlhttpRequest({method:"POST",url:"https://telemetry.thebluecorner.net/heartbeat",headers:{"Content-Type":"application/json"},data:JSON.stringify({uuid:e.uuid,version:t,browser:n,os:s}),onload:t=>{200!==t.status&&c("Failed to send heartbeat:",t.statusText)},onerror:t=>{c("Error sending heartbeat:",t)}})}async qe(t=navigator.userAgent){return(t=t||"").includes("OPR/")||t.includes("Opera")?"Opera":t.includes("Edg/")?"Edge":t.includes("Vivaldi")?"Vivaldi":t.includes("YaBrowser")?"Yandex":t.includes("Kiwi")?"Kiwi":t.includes("Brave")?"Brave":t.includes("Firefox/")?"Firefox":t.includes("Chrome/")?"Chrome":t.includes("Safari/")?"Safari":navigator.brave&&"function"==typeof navigator.brave.isBrave&&await navigator.brave.isBrave()?"Brave":"Unknown"}Je(t=navigator.userAgent){return/Windows NT 11/i.test(t=t||"")?"Windows 11":/Windows NT 10/i.test(t)?"Windows 10":/Windows NT 6\.3/i.test(t)?"Windows 8.1":/Windows NT 6\.2/i.test(t)?"Windows 8":/Windows NT 6\.1/i.test(t)?"Windows 7":/Windows NT 6\.0/i.test(t)?"Windows Vista":/Windows NT 5\.1|Windows XP/i.test(t)?"Windows XP":/Mac OS X 10[_\.]15/i.test(t)?"macOS Catalina":/Mac OS X 10[_\.]14/i.test(t)?"macOS Mojave":/Mac OS X 10[_\.]13/i.test(t)?"macOS High Sierra":/Mac OS X 10[_\.]12/i.test(t)?"macOS Sierra":/Mac OS X 10[_\.]11/i.test(t)?"OS X El Capitan":/Mac OS X 10[_\.]10/i.test(t)?"OS X Yosemite":/Mac OS X 10[_\.]/i.test(t)?"macOS":/Android/i.test(t)?"Android":/iPhone|iPad|iPod/i.test(t)?"iOS":/Linux/i.test(t)?"Linux":"Unknown"}}(et);tt.W(it);var nt=JSON.parse(GM_getValue("bmTemplates","{}"));et.Re(nt);var st=JSON.parse(GM_getValue("bmUserSettings","{}"));if(0==Object.keys(st).length){const t=crypto.randomUUID();GM.setValue("bmUserSettings",JSON.stringify({uuid:t}))}setInterval(()=>it.Ye(X),18e5);var ot=st?.telemetry;if(null==ot||ot>1){const t=new class extends D{constructor(t,i,n,s){super(t,i),e(this,E),this.window=null,this.Ot="bm-g",this.Bt=document.body,this.Xe=n,this.uuid=s}async Pt(){if(document.querySelector(`#${this.Ot}`))return void this.Lt("Telemetry window already exists!");const t=await this.O.qe(navigator.userAgent),e=this.O.Je(navigator.userAgent);this.window=this.U({id:this.Ot,class:"bm-N",style:"height: 80vh; z-index: 9998;"}).U({class:"bm-h"}).U({class:"bm-E bm-d"}).q(1,{textContent:`${this.name} Telemetry`}).P().P().J().P().U({class:"bm-E bm-x",style:"gap: 1.5ch; flex-wrap: wrap;"}).yt({textContent:"Enable Telemetry"},(t,e)=>{e.onclick=()=>{i(this,E,V).call(this,this.Xe);const t=document.getElementById(this.Ot);t?.remove()}}).P().yt({textContent:"Disable Telemetry"},(t,e)=>{e.onclick=()=>{i(this,E,V).call(this,0);const t=document.getElementById(this.Ot);t?.remove()}}).P().yt({textContent:"More Information"},(t,e)=>{e.onclick=()=>{window.open("https://github.com/SwingTheVine/Wplace-TelemetryServer#telemetry-data","_blank","noopener noreferrer")}}).P().P().U({class:"bm-E bm-A"}).U({class:"bm-E"}).q(2,{textContent:"Legal"}).P().G({textContent:`We collect anonymous telemetry data such as your browser, OS, and script version to make the experience better for everyone. The data is never shared personally. The data is never sold. You can turn this off by pressing the "Disable" button, but keeping it on helps us improve features and reliability faster. Thank you for supporting ${this.name}!`}).P().P().J().P().U({class:"bm-E"}).q(2,{textContent:"Non-Legal Summary"}).P().G({innerHTML:'You can disable telemetry by pressing the "Disable" button. If you would like to read more about what information we collect, press the "More Information" button.
This is the data stored on our servers:'}).P().ot().lt({innerHTML:`A unique identifier (UUIDv4) generated by Blue Marble. This enables our telemetry to function without tracking your actual user ID.
Your UUID is: ${r(this.uuid)}`}).P().lt({innerHTML:`The version of Blue Marble you are using.
Your version is: ${r(this.version)}`}).P().lt({innerHTML:`Your browser type, which is used to determine Blue Marble outages and browser popularity.
Your browser type is: ${r(t)}`}).P().lt({innerHTML:`Your OS type, which is used to determine Blue Marble outages and OS popularity.
Your OS type is: ${r(e)}`}).P().lt({innerHTML:"The date and time that Blue Marble sent the telemetry information."}).P().P().G({innerHTML:'All of the data mentioned above is aggregated every hour. This means every hour, anything that could even remotly be considered "personal data" is deleted from our server. Here, "aggregated" data means things like "42 people used Blue Marble on Google Chrome this hour", which can\'t be used to identify anyone in particular.'}).P().P().P().P().P()._(this.Bt)}}(J,X,1,st?.uuid);t.W(it),t.Pt()}tt.Pt(),it.Ve(tt),new MutationObserver((t,e)=>{const i=document.querySelector("#color-1");if(!i)return;let n=document.querySelector("#bm-z");if(!n){n=document.createElement("button"),n.id="bm-z",n.textContent="Move ↑",n.className="btn btn-soft",n.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 ↑"};const t=i.parentNode.parentNode.parentNode.parentNode.querySelector("h2");t.parentNode?.appendChild(n)}}).observe(document.body,{childList:!0,subtree:!0}),l(`%c${J}%c (${X}) userscript has loaded!`,"color: cornflowerblue;","")})(); \ No newline at end of file +(()=>{var t=t=>{throw TypeError(t)},e=(e,i,n)=>i.has(e)?t("Cannot add the same private member more than once"):i instanceof WeakSet?i.add(e):i.set(e,n),i=(e,i,n)=>(((e,i)=>{i.has(e)||t("Cannot access private method")})(e,i),n);function n(t){return new Promise(e=>setTimeout(e,t))}function s(t){return(new Intl.NumberFormat).format(t)}function o(t){return new Intl.NumberFormat(void 0,{style:"percent",t:2,i:2}).format(t)}function a(t){return t.toLocaleString(void 0,{o:"long",l:"numeric",h:"2-digit",m:"2-digit",u:"2-digit"})}function r(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}function l(...t){(0,console.log)(...t)}function c(...t){(0,console.error)(...t)}function h(...t){(0,console.warn)(...t)}function m(t,e){if(0===t)return e[0];let i="";const n=e.length;for(;t>0;)i=e[t%n]+i,t=Math.floor(t/n);return i}function d(t,e){let i=0;const n=e.length;for(const s of t){const t=e.indexOf(s);-1==t&&c(`Invalid character '${s}' encountered whilst decoding! Is the decode alphabet/base incorrect?`),i=i*n+t}return i}function u(t){let e="";for(let i=0;i(t/=255)<=.03928?t/12.92:Math.pow((t+.055)/1.055,2.4));return.2126*e[0]+.7152*e[1]+.0722*e[2]}function g(t,e,i){return Array.isArray(t)&&([t,e,i]=t),(1<<24|t<<16|e<<8|i).toString(16).slice(1)}var f,w,x,y,$,v=[{id:0,premium:!1,name:"Transparent",rgb:[0,0,0]},{id:1,premium:!1,name:"Black",rgb:[0,0,0]},{id:2,premium:!1,name:"Dark Gray",rgb:[60,60,60]},{id:3,premium:!1,name:"Gray",rgb:[120,120,120]},{id:4,premium:!1,name:"Light Gray",rgb:[210,210,210]},{id:5,premium:!1,name:"White",rgb:[255,255,255]},{id:6,premium:!1,name:"Deep Red",rgb:[96,0,24]},{id:7,premium:!1,name:"Red",rgb:[237,28,36]},{id:8,premium:!1,name:"Orange",rgb:[255,127,39]},{id:9,premium:!1,name:"Gold",rgb:[246,170,9]},{id:10,premium:!1,name:"Yellow",rgb:[249,221,59]},{id:11,premium:!1,name:"Light Yellow",rgb:[255,250,188]},{id:12,premium:!1,name:"Dark Green",rgb:[14,185,104]},{id:13,premium:!1,name:"Green",rgb:[19,230,123]},{id:14,premium:!1,name:"Light Green",rgb:[135,255,94]},{id:15,premium:!1,name:"Dark Teal",rgb:[12,129,110]},{id:16,premium:!1,name:"Teal",rgb:[16,174,166]},{id:17,premium:!1,name:"Light Teal",rgb:[19,225,190]},{id:18,premium:!1,name:"Dark Blue",rgb:[40,80,158]},{id:19,premium:!1,name:"Blue",rgb:[64,147,228]},{id:20,premium:!1,name:"Cyan",rgb:[96,247,242]},{id:21,premium:!1,name:"Indigo",rgb:[107,80,246]},{id:22,premium:!1,name:"Light Indigo",rgb:[153,177,251]},{id:23,premium:!1,name:"Dark Purple",rgb:[120,12,153]},{id:24,premium:!1,name:"Purple",rgb:[170,56,185]},{id:25,premium:!1,name:"Light Purple",rgb:[224,159,249]},{id:26,premium:!1,name:"Dark Pink",rgb:[203,0,122]},{id:27,premium:!1,name:"Pink",rgb:[236,31,128]},{id:28,premium:!1,name:"Light Pink",rgb:[243,141,169]},{id:29,premium:!1,name:"Dark Brown",rgb:[104,70,52]},{id:30,premium:!1,name:"Brown",rgb:[149,104,42]},{id:31,premium:!1,name:"Beige",rgb:[248,178,119]},{id:32,premium:!0,name:"Medium Gray",rgb:[170,170,170]},{id:33,premium:!0,name:"Dark Red",rgb:[165,14,30]},{id:34,premium:!0,name:"Light Red",rgb:[250,128,114]},{id:35,premium:!0,name:"Dark Orange",rgb:[228,92,26]},{id:36,premium:!0,name:"Light Tan",rgb:[214,181,148]},{id:37,premium:!0,name:"Dark Goldenrod",rgb:[156,132,49]},{id:38,premium:!0,name:"Goldenrod",rgb:[197,173,49]},{id:39,premium:!0,name:"Light Goldenrod",rgb:[232,212,95]},{id:40,premium:!0,name:"Dark Olive",rgb:[74,107,58]},{id:41,premium:!0,name:"Olive",rgb:[90,148,74]},{id:42,premium:!0,name:"Light Olive",rgb:[132,197,115]},{id:43,premium:!0,name:"Dark Cyan",rgb:[15,121,159]},{id:44,premium:!0,name:"Light Cyan",rgb:[187,250,242]},{id:45,premium:!0,name:"Light Blue",rgb:[125,199,255]},{id:46,premium:!0,name:"Dark Indigo",rgb:[77,49,184]},{id:47,premium:!0,name:"Dark Slate Blue",rgb:[74,66,132]},{id:48,premium:!0,name:"Slate Blue",rgb:[122,113,196]},{id:49,premium:!0,name:"Light Slate Blue",rgb:[181,174,241]},{id:50,premium:!0,name:"Light Brown",rgb:[219,164,99]},{id:51,premium:!0,name:"Dark Beige",rgb:[209,128,81]},{id:52,premium:!0,name:"Light Beige",rgb:[255,197,165]},{id:53,premium:!0,name:"Dark Peach",rgb:[155,82,73]},{id:54,premium:!0,name:"Peach",rgb:[209,128,120]},{id:55,premium:!0,name:"Light Peach",rgb:[250,182,164]},{id:56,premium:!0,name:"Dark Tan",rgb:[123,99,82]},{id:57,premium:!0,name:"Tan",rgb:[156,132,107]},{id:58,premium:!0,name:"Dark Slate",rgb:[51,57,65]},{id:59,premium:!0,name:"Slate",rgb:[109,117,141]},{id:60,premium:!0,name:"Light Slate",rgb:[179,185,209]},{id:61,premium:!0,name:"Dark Stone",rgb:[109,100,63]},{id:62,premium:!0,name:"Stone",rgb:[148,140,107]},{id:63,premium:!0,name:"Light Stone",rgb:[205,197,158]}],M=class{constructor(t,i){e(this,f),this.name=t,this.version=i,this.p=null,this.$=null,this.v="bm-l",this.M=null,this.C=null,this.T=[]}S(t){this.p=t}k(t){this.$=t}D(){return this.T.length>0&&(this.C=this.T.pop()),this}L(t){t?.appendChild(this.M),this.M=null,this.C=null,this.T=[]}H(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"div",{},t)),this}O(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"p",{},t)),this}N(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"small",{},t)),this}B(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"span",{},t)),this}I(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"details",{},t)),this}P(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"summary",{},t)),this}A(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"img",{},t)),this}W(t,e={},n=()=>{}){return n(this,i(this,f,w).call(this,"h"+t,{},e)),this}F(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"hr",{},t)),this}V(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"br",{},t)),this}_(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"form",{},t)),this}U(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"fieldset",{},t)),this}G(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"legend",{},t)),this}R(t={},e=()=>{}){const n={};t.textContent?(n.textContent=t.textContent,delete t.textContent):t.innerHTML&&(n.innerHTML=t.innerHTML,delete t.textContent);const s=i(this,f,w).call(this,"label",n),o=i(this,f,w).call(this,"input",{type:"checkbox"},t);return s.insertBefore(o,s.firstChild),this.D(),e(this,s,o),this}j(t={},e=()=>{}){const n=i(this,f,w).call(this,"label",{textContent:t.textContent??"",for:t.id??""});return delete t.textContent,this.D(),e(this,n,i(this,f,w).call(this,"select",{},t)),this}Y(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"option",{},t)),this}J(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"ol",{},t)),this}X(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"ul",{},t)),this}q(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"menu",{},t)),this}Z(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"li",{},t)),this}K(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"table",{},t)),this}tt(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"caption",{},t)),this}et(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"thead",{},t)),this}it(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"tbody",{},t)),this}nt(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"tfoot",{},t)),this}st(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"tr",{},t)),this}ot(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"th",{},t)),this}rt(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"td",{},t)),this}lt(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"button",{},t)),this}ct(t={},e=()=>{}){const n=t.title??t.textContent??"Help: No info";delete t.textContent,t.title=`Help: ${n}`;const s={textContent:"?",className:"bm-R",onclick:()=>{this.ht(this.v,n)}};return e(this,i(this,f,w).call(this,"button",s,t)),this}dt(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"input",{},t)),this}ut(t={},e=()=>{}){const n=t.textContent??"";delete t.textContent;const s=i(this,f,w).call(this,"div"),o=i(this,f,w).call(this,"input",{type:"file",tabindex:"-1","aria-hidden":"true"},t);this.D();const a=i(this,f,w).call(this,"button",{textContent:n});return this.D(),this.D(),a.addEventListener("click",()=>{o.click()}),o.addEventListener("change",()=>{a.style.maxWidth=`${a.offsetWidth}px`,o.files.length>0?a.textContent=o.files[0].name:a.textContent=n}),e(this,s,o,a),this}bt(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"textarea",{},t)),this}gt(t={},e=()=>{}){return e(this,i(this,f,w).call(this,"div",{class:"bm-L"},t)),this}ft(t=Date.now(),e=500,n={},s=()=>{}){const o="bm-P",a=n?.id||o+"-"+crypto.randomUUID().slice(0,8),r={class:o},l=i(this,f,w).call(this,"time",r,n);return l.id=a,l.dataset.endDate=t,setInterval(()=>{if(!l.isConnected)return;const t=Math.max(l.dataset.endDate-Date.now(),0),e=Math.floor(t/1e3),i=Math.floor(e/3600),n=Math.floor(e%60),s=Math.floor(e%3600/60);l.setAttribute("datetime",`PT${i}H${s}M${n}S`),l.textContent=String(i).padStart(2,"0")+":"+String(s).padStart(2,"0")+":"+String(n).padStart(2,"0")},e),s(this,l),this}ht(t,e,i=!1){const n=document.getElementById(t.replace(/^#/,""));n&&(n instanceof HTMLInputElement?n.value=e:i?n.textContent=e:n.innerHTML=e)}wt(t){if(t.disabled)return;t.disabled=!0,t.style.textDecoration="none";const e=t.closest(".bm-N"),i=t.closest(".bm-L"),n=e.querySelector("h1"),s=e.querySelector(".bm-h");if(e.parentElement.append(e),"expanded"==t.dataset.buttonStatus){s.style.height=s.scrollHeight+"px",e.style.width=e.scrollWidth+"px",s.style.height="0",s.addEventListener("transitionend",function e(){s.style.display="none",t.disabled=!1,t.style.textDecoration="",s.removeEventListener("transitionend",e)});const i=n.cloneNode(!0),o=i.textContent;t.nextElementSibling.appendChild(i),t.textContent="▶",t.dataset.buttonStatus="collapsed",t.ariaLabel=`Unminimize window "${o}"`}else{const n=i.querySelector("h1"),o=n.textContent;n.remove(),s.style.display="",s.style.height="0",e.style.width="",s.style.height=s.scrollHeight+"px",s.addEventListener("transitionend",function e(){s.style.height="",t.disabled=!1,t.style.textDecoration="",s.removeEventListener("transitionend",e)}),t.textContent="▼",t.dataset.buttonStatus="expanded",t.ariaLabel=`Minimize window "${o}"`}}xt(t,e){const i=document.querySelector(t),n=document.querySelector(e);if(!i||!n)return void this.yt(`Can not drag! ${i?"":"moveMe"} ${i||n?"":"and "}${n?"":"iMoveThings "}was not found!`);let s,o=!1,a=0,r=null,l=0,c=0,h=0,m=0,d=null;const u=()=>{if(o){const t=Math.abs(l-h),e=Math.abs(c-m);(t>.5||e>.5)&&(l=h,c=m,i.style.transform=`translate(${l}px, ${c}px)`,i.style.left="0px",i.style.top="0px",i.style.right=""),r=requestAnimationFrame(u)}},b=(t,e)=>{o=!0,d=i.getBoundingClientRect(),s=t-d.left,a=e-d.top;const b=window.getComputedStyle(i).transform;if(b&&"none"!==b){const t=new DOMMatrix(b);l=t.m41,c=t.m42}else l=d.left,c=d.top;h=l,m=c,document.body.style.userSelect="none",n.classList.add("bm-F"),document.addEventListener("mousemove",g),document.addEventListener("touchmove",f,{passive:!1}),document.addEventListener("mouseup",p),document.addEventListener("touchend",p),document.addEventListener("touchcancel",p),r&&cancelAnimationFrame(r),u()},p=()=>{o=!1,r&&(cancelAnimationFrame(r),r=null),document.body.style.userSelect="",n.classList.remove("bm-F"),document.removeEventListener("mousemove",g),document.removeEventListener("touchmove",f),document.removeEventListener("mouseup",p),document.removeEventListener("touchend",p),document.removeEventListener("touchcancel",p)},g=t=>{o&&d&&(h=t.clientX-s,m=t.clientY-a)},f=t=>{if(o&&d){const e=t.touches[0];if(!e)return;h=e.clientX-s,m=e.clientY-a,t.preventDefault()}};n.addEventListener("mousedown",function(t){t.preventDefault(),b(t.clientX,t.clientY)}),n.addEventListener("touchstart",function(t){const e=t?.touches?.[0];e&&(b(e.clientX,e.clientY),t.preventDefault())},{passive:!1})}$t(t){(0,console.info)(`${this.name}: ${t}`),this.ht(this.v,"Status: "+t,!0)}yt(t){(0,console.error)(`${this.name}: ${t}`),this.ht(this.v,"Error: "+t,!0)}};f=new WeakSet,w=function(t,e={},n={}){const s=document.createElement(t);this.M?(this.C?.appendChild(s),this.T.push(this.C),this.C=s):(this.M=s,this.C=s);for(const[t,n]of Object.entries(e))i(this,f,x).call(this,s,t,n);for(const[t,e]of Object.entries(n))i(this,f,x).call(this,s,t,e);return s},x=function(t,e,i){"class"==e?t.classList.add(...i.split(/\s+/)):"for"==e?t.htmlFor=i:"tabindex"==e?t.tabIndex=Number(i):"readonly"==e?t.readOnly="true"==i||"1"==i:"maxlength"==e?t.maxLength=Number(i):e.startsWith("data")?t.dataset[e.slice(5).split("-").map((t,e)=>0==e?t:t[0].toUpperCase()+t.slice(1)).join("")]=i:e.startsWith("aria")?t.setAttribute(e,i):t[e]=i};var C,T,S,k,D,L=class extends M{constructor(t,i){super(t,i),e(this,y),this.window=null,this.vt="bm-N-settings",this.Mt=document.body}Ct(){document.querySelector(`#${this.vt}`)?document.querySelector(`#${this.vt}`).remove():(this.window=this.H({id:this.vt,class:"bm-N"}).gt().lt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Color Filter"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.wt(e),e.ontouchend=()=>{e.click()}}).D().H().D().H({class:"bm-x"}).lt({class:"bm-n",textContent:"✖","aria-label":'Close window "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.vt}`)?.remove()},e.ontouchend=()=>{e.click()}}).D().D().D().H({class:"bm-h"}).H({class:"bm-E bm-d"}).W(1,{textContent:"Settings"}).D().D().F().D().O({textContent:"Settings take 5 seconds to save."}).D().H({class:"bm-E bm-A"},(t,e)=>{this.Tt(),this.St()}).D().D().D().L(this.Mt),this.xt(`#${this.vt}.bm-N`,`#${this.vt} .bm-L`))}Tt(){i(this,y,$).call(this,"Pixel Highlight")}St(){i(this,y,$).call(this,"Template")}};y=new WeakSet,$=function(t){this.window=this.H({class:"bm-E"}).W(2,{textContent:t}).D().F().D().O({innerHTML:`An error occured loading the ${t} category. SettingsManager failed to override the ${t} function inside WindowSettings.`}).D().D()},C=new WeakSet,T=function(t,e){t.disabled=!0;const i=t.dataset.status,n=this.kt?.highlight??[[1,0,1],[2,0,0],[1,-1,0],[1,1,0],[1,0,-1]];let s=[2,0,0];const o=n;switch(i){case"Disabled":t.dataset.status="Incorrect",t.ariaLabel="Sub-pixel incorrect",s=[1,...e];break;case"Incorrect":t.dataset.status="Template",t.ariaLabel="Sub-pixel template",s=[2,...e];break;case"Template":t.dataset.status="Disabled",t.ariaLabel="Sub-pixel disabled",s=[0,...e];break}const a=n.findIndex(([,t,e])=>t==s[1]&&e==s[2]);0!=s[0]?-1!=a?o[a]=s:o.push(s):-1!=a&&o.splice(a,1),this.kt.highlight=o,t.disabled=!1},S=async function(t){const e=document.querySelectorAll(".bm-Y button");for(const t of e)t.disabled=!0;let i=[0,0,0,0,2,0,0,0,0];switch(t){case"Cross":i=[0,1,0,1,2,1,0,1,0];break;case"X":i=[1,0,1,0,2,0,1,0,1];break;case"Full":i=[2,2,2,2,2,2,2,2,2];break}const s=document.querySelector(".bm-1g")?.childNodes??[];for(let t=0;t{const[n,s,o,a]=e.split(",").map(Number);(s>>24==0?0:s.get(e)??-2;const a=o.get(n);o.set(n,a?a+1:1)}return console.log(o),o};var O=class{constructor(){this.Et=Math.ceil(80/1300*window.innerWidth),this.Yt=v.slice(1)}Jt(t){const e=document.createElement("div");for(let t=0;t{t.parentNode.childElementCount<=1?t.parentNode.remove():t.remove()},e.appendChild(t)}t.appendChild(e)}},N=class extends HTMLElement{};customElements.define("confetti-piece",N);var B,I,P,A,W,F,V,z,_,U=class extends M{constructor(t,e){super(t,e),this.window=null,this.vt="bm-i",this.Mt=document.body}Ct(){document.querySelector(`#${this.vt}`)?document.querySelector(`#${this.vt}`).remove():(this.window=this.H({id:this.vt,class:"bm-N"},(t,e)=>{}).gt().lt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Credits"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.wt(e),e.ontouchend=()=>{e.click()}}).D().H().D().lt({class:"bm-n",textContent:"✖","aria-label":'Close window "Credits"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.vt}`)?.remove()},e.ontouchend=()=>{e.click()}}).D().D().H({class:"bm-h"}).H({class:"bm-E bm-d"}).W(1,{textContent:"Credits"}).D().D().F().D().H({class:"bm-E bm-A"}).B({role:"img","aria-label":this.name}).B({innerHTML:"\n██████╗ ██╗ ██╗ ██╗███████╗\n██╔══██╗██║ ██║ ██║██╔════╝\n██████╔╝██║ ██║ ██║█████╗ \n██╔══██╗██║ ██║ ██║██╔══╝ \n██████╔╝███████╗╚██████╔╝███████╗\n╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝\n\n███╗ ███╗ █████╗ ██████╗ ██████╗ ██╗ ███████╗\n████╗ ████║██╔══██╗██╔══██╗██╔══██╗██║ ██╔════╝\n██╔████╔██║███████║██████╔╝██████╔╝██║ █████╗ \n██║╚██╔╝██║██╔══██║██╔══██╗██╔══██╗██║ ██╔══╝ \n██║ ╚═╝ ██║██║ ██║██║ ██║██████╔╝███████╗███████╗\n╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚══════╝\n",class:"bm-Q","aria-hidden":"true"}).D().D().V().D().F().D().V().D().B({textContent:'"Blue Marble" userscript is made by SwingTheVine.'}).D().V().D().B({innerHTML:'The Blue Marble Website is made by crqch.'}).D().V().D().B({textContent:`The Blue Marble Website used until ${a(new Date(175606932e4))} was made by Camille Daguin.`}).D().V().D().B({textContent:'The favicon "Blue Marble" is owned by NASA. (The image of the Earth is owned by NASA)'}).D().V().D().B({textContent:"Special Thanks:"}).D().X().Z({textContent:"Espresso, Meqa, and Robot for moderating SwingTheVine's community."}).D().Z({innerHTML:'nof, darkness for creating similar userscripts!'}).D().Z({innerHTML:'Wonda for the Blue Marble banner image!'}).D().Z({innerHTML:'BullStein, allanf181 for being early beta testers!'}).D().Z({innerHTML:'guidu_ and Nick-machado for the original "Minimize" Button code!'}).D().Z({innerHTML:'Nomad and Gustav for the tutorials!'}).D().Z({innerHTML:'cfp for creating the template overlay that Blue Marble was based on!'}).D().Z({innerHTML:'Force Network for hosting the telemetry server!'}).D().Z({innerHTML:'TheBlueCorner for getting me interested in online pixel canvases!'}).D().D().V().D().B({innerHTML:'Donators:'}).D().X().Z({textContent:"Espresso"}).D().Z({textContent:"BEST FAN"}).D().Z({textContent:"FuchsDresden"}).D().Z({textContent:"Jack"}).D().Z({textContent:"raiken_au"}).D().Z({textContent:"Jacob"}).D().Z({textContent:"StupidOne"}).D().Z({textContent:"2 Anonymous Supporters"}).D().D().D().D().D().L(this.Mt),this.xt(`#${this.vt}.bm-N`,`#${this.vt} .bm-L`))}},G=class extends M{constructor(t){super(t.name,t.version),e(this,B),this.window=null,this.vt="bm-p",this.Xt="bm-y",this.Mt=document.body,this.qt=t.p?.qt,this.Zt='',this.Qt='';const{palette:i,jt:n}=this.qt.Kt;this.palette=i,this.te=0,this.ee=0,this.ie=new Map,this.ne=new Map,this.se=0,this.oe=0,this.timeRemaining=0,this.ae="",this.sortPrimary="id",this.sortSecondary="ascending",this.showUnused=!1}Ct(){if(document.querySelector(`#${this.vt}`))return void document.querySelector(`#${this.vt}`).remove();this.window=this.H({id:this.vt,class:"bm-N"},(t,e)=>{}).gt().lt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Color Filter"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.wt(e),e.ontouchend=()=>{e.click()}}).D().H().D().H({class:"bm-x"}).lt({class:"bm-n",textContent:"🗗","aria-label":'Switch to windowed mode for "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.vt}`)?.remove(),this.re()},e.ontouchend=()=>{e.click()}}).D().lt({class:"bm-n",textContent:"✖","aria-label":'Close window "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.vt}`)?.remove()},e.ontouchend=()=>{e.click()}}).D().D().D().H({class:"bm-h"}).H({class:"bm-E bm-d"}).W(1,{textContent:"Color Filter"}).D().D().F().D().H({class:"bm-E bm-s bm-d",style:"gap: 1.5ch;"}).lt({textContent:"Hide All Colors"},(t,e)=>{e.onclick=()=>i(this,B,A).call(this,!1)}).D().lt({textContent:"Refresh Data"},(t,e)=>{e.onclick=()=>{e.disabled=!0,this.le(),e.disabled=!1}}).D().lt({textContent:"Show All Colors"},(t,e)=>{e.onclick=()=>i(this,B,A).call(this,!0)}).D().D().H({class:"bm-E bm-A"}).H({class:"bm-E",style:"margin-left: 2.5ch; margin-right: 2.5ch;"}).H({class:"bm-E"}).B({id:"bm-e",innerHTML:"Tiles Loaded: 0 / ???"}).D().V().D().B({id:"bm-9",innerHTML:"Correct Pixels: ???"}).D().V().D().B({id:"bm-f",innerHTML:"Total Pixels: ???"}).D().V().D().B({id:"bm-4",innerHTML:"Complete: ??? (???)"}).D().V().D().B({id:"bm-5",innerHTML:"??? ???"}).D().D().H({class:"bm-E"}).O({innerHTML:`Press the 🗗 button to make this window smaller. Colors with the icon ${this.Zt.replace("{e.onclick=t=>{t.preventDefault();const e=new FormData(document.querySelector(`#${this.vt} form`)),n={};for(const[t,i]of e)n[t]=i;console.log(`Primary: ${n.sortPrimary}; Secondary: ${n.sortSecondary}; Unused: ${"on"==n.showUnused}`),i(this,B,P).call(this,n.sortPrimary,n.sortSecondary,"on"==n.showUnused)}}).D().D().D().D().D().D().D().L(this.Mt),this.xt(`#${this.vt}.bm-N`,`#${this.vt} .bm-L`);const t=document.querySelector(`#${this.vt} .bm-E.bm-A`);i(this,B,I).call(this,t),i(this,B,P).call(this,this.sortPrimary,this.sortSecondary,this.showUnused),this.ht("#bm-e",`Tiles Loaded: ${s(this.te)} / ${s(this.ee)}`),this.ht("#bm-9",`Correct Pixels: ${s(this.se)}`),this.ht("#bm-f",`Total Pixels: ${s(this.oe)}`),this.ht("#bm-4",`Remaining: ${s((this.oe||0)-(this.se||0))} (${o(((this.oe||0)-(this.se||0))/(this.oe||1))})`),this.ht("#bm-5",`Completed at: `)}re(){if(document.querySelector(`#${this.vt}`))return void document.querySelector(`#${this.vt}`).remove();this.window=this.H({id:this.vt,class:"bm-N bm-G"}).gt().lt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Color Filter"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>{const i=document.querySelector("#bm-X");i&&(i.style.display="expanded"==e.dataset.buttonStatus?"none":""),t.wt(e)},e.ontouchend=()=>{e.click()}}).D().H().B({id:"bm-X",class:"bm-L-text",style:"font-weight: 700;"}).D().D().H({class:"bm-x"}).lt({class:"bm-n",textContent:"🗖","aria-label":'Switch to fullscreen mode for "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.vt}`)?.remove(),this.Ct()},e.ontouchend=()=>{e.click()}}).D().lt({class:"bm-n",textContent:"✖","aria-label":'Close window "Color Filter"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.vt}`)?.remove()},e.ontouchend=()=>{e.click()}}).D().D().D().H({class:"bm-h"}).H({class:"bm-E bm-d"}).W(1,{textContent:"Color Filter"}).D().D().F().D().H({class:"bm-E bm-s bm-d",style:"gap: 1.5ch;"}).lt({textContent:"None"},(t,e)=>{e.onclick=()=>i(this,B,A).call(this,!1)}).D().lt({textContent:"Refresh"},(t,e)=>{e.onclick=()=>{e.disabled=!0,this.le(),e.disabled=!1}}).D().lt({textContent:"All"},(t,e)=>{e.onclick=()=>i(this,B,A).call(this,!0)}).D().D().H({class:"bm-E bm-A"}).D().D().D().L(this.Mt),this.xt(`#${this.vt}.bm-N`,`#${this.vt} .bm-L`);const t=document.querySelector(`#${this.vt} .bm-E.bm-A`);i(this,B,I).call(this,t),i(this,B,P).call(this,this.sortPrimary,this.sortSecondary,this.showUnused)}le(){i(this,B,W).call(this);const t=document.querySelector(`#${this.Xt}`),e={};for(const t of this.palette){const i=this.ie.get(t.id)??0,n=s(i);let a=0,r="0",l=o(1);0!=i&&(a=this.ne.get(t.id)??"???","number"!=typeof a&&this.te==this.ee&&t.id&&(a=0),r="string"==typeof a?a:s(a),l=isNaN(a/i)?"???":o(a/i));const c=parseInt(i)-parseInt(a);e[t.id]={ce:i,he:n,me:a,de:r,ue:l,be:c}}if(document.querySelector("#bm-X")){const t=this.se.toString().length>7?this.se.toString().slice(0,2)+"…"+this.se.toString().slice(-3):this.se.toString(),e=this.oe.toString().length>7?this.oe.toString().slice(0,2)+"…"+this.oe.toString().slice(-3):this.oe.toString();this.ht("#bm-X",`${t}/${e}`,!0)}if(!t)return e;const n=Array.from(t.children);for(const t of n){const i=parseInt(t.dataset.id),{me:n,de:s,ue:o,ce:a,he:r,be:l}=e[i];t.dataset.correct=Number.isNaN(parseInt(n))?"0":n,t.dataset.total=a,t.dataset.percent="%"==o.slice(-1)?o.slice(0,-1):"0",t.dataset.incorrect=l||0;const c=document.querySelector(`#${this.vt} .bm-t[data-id="${i}"] .bm-6`);c&&(c.textContent=`${s} / ${r}`);const h=document.querySelector(`#${this.vt} .bm-t[data-id="${i}"] .bm-3`);h&&(h.textContent=`${"number"!=typeof l||isNaN(l)?"???":l} incorrect pixel${1==l?"":"s"}. Completed: ${o}`)}i(this,B,P).call(this,this.sortPrimary,this.sortSecondary,this.showUnused)}};B=new WeakSet,I=function(t){const e=t.closest(`#${this.vt}`)?.classList.contains("bm-G");console.log(`Is Windowed Mode: ${e}`);const i=new M(this.name,this.version);i.H({id:this.Xt});const n=this.le();for(const t of this.palette){const s="#"+g(t.rgb).toUpperCase(),o=p(t.rgb);let a=1.05/(o+.05)>(o+.05)/.05?"white":"black";t.id||(a="transparent");const r="white"==a?"bm-b":"bm-c",{me:l,de:c,ue:h,ce:m,he:d,be:u}=n[t.id],b=!!this.qt.pe.get(t.id);if(e){const e=`background-size: auto 100%; background-repeat: repeat-x; background-image: url("data:image/svg+xml;utf8,");`;i.H({class:"bm-E bm-t bm-s","data-id":t.id,"data-name":t.name,"data-premium":+t.premium,"data-correct":Number.isNaN(parseInt(l))?"0":l,"data-total":m,"data-percent":"%"==h.slice(-1)?h.slice(0,-1):"0","data-incorrect":u||0}).H({class:"bm-7",style:`background-color: rgb(${t.rgb?.map(t=>Number(t)||0).join(",")});${t.premium?e:""}`}).lt({class:"bm-u "+r,"data-state":b?"hidden":"shown","aria-label":b?`Show the color ${t.name||""} on templates.`:`Hide the color ${t.name||""} on templates.`,innerHTML:b?this.Qt.replace("{i.onclick=()=>{i.style.textDecoration="none",i.disabled=!0,"shown"==i.dataset.state?(i.innerHTML=this.Qt.replace("Number(t)||0).join(",")});`}).lt({class:"bm-u "+r,"data-state":b?"hidden":"shown","aria-label":b?`Show the color ${t.name||""} on templates.`:`Hide the color ${t.name||""} on templates.`,innerHTML:b?this.Qt.replace("{i.onclick=()=>{i.style.textDecoration="none",i.disabled=!0,"shown"==i.dataset.state?(i.innerHTML=this.Qt.replace("{const o=n.getAttribute("data-"+t),a=s.getAttribute("data-"+t),r=parseFloat(o),l=parseFloat(a),c=!isNaN(r),h=!isNaN(l);if(i?n.classList.remove("bm-B"):Number(n.getAttribute("data-total"))||n.classList.add("bm-B"),c&&h)return"ascending"===e?r-l:l-r;{const t=o.toLowerCase(),i=a.toLowerCase();return ti?"ascending"===e?1:-1:0}}),s.forEach(t=>n.appendChild(t))},A=function(t){const e=document.querySelector(`#${this.Xt}`),i=Array.from(e.children);for(const e of i){if(e.classList?.contains("bm-B"))continue;const i=e.querySelector(".bm-7 button");("hidden"!=i.dataset.state||t)&&("shown"==i.dataset.state&&t||i.click())}},W=function(){this.oe=0,this.se=0,this.ne=new Map,this.ie=new Map;for(const t of this.qt.ge){const e=t.Bt?.total??0;this.oe+=e??0;const i=t.Bt?.colors??new Map;for(const[t,e]of i){const i=Number(e)||0,n=this.ie.get(t)??0;this.ie.set(t,n+i)}const n=t.Bt?.correct??{};this.te+=Object.keys(n).length,this.ee+=Object.keys(t.Ht).length;for(const t of Object.values(n))for(const[e,i]of t){const t=Number(i)||0;this.se+=t;const n=this.ne.get(e)??0;this.ne.set(e,n+t)}}console.log(`Tiles loaded: ${this.te} / ${this.ee}`),this.se>=this.oe&&this.oe&&this.te==this.ee&&(new O).Jt(document.querySelector(`#${this.vt}`)),this.timeRemaining=new Date(30*(this.oe-this.se)*1e3+Date.now()),this.ae=a(this.timeRemaining)};var R=class extends M{constructor(t,i,n,s=void 0){super(t,i),e(this,F),this.window=null,this.vt="bm-m",this.Mt=document.body,this.fe=JSON.parse(GM_getValue("bmTemplates","{}")),this.scriptVersion=this.fe?.scriptVersion,this.schemaVersion=this.fe?.schemaVersion,this.we=void 0,this.xe=n,this.qt=s}Ct(){if(document.querySelector(`#${this.vt}`))return void document.querySelector(`#${this.vt}`).remove();let t="";document.querySelector("#bm-w")||(t=t.concat("z-index: 9001;").trim()),this.window=this.H({id:this.vt,class:"bm-N",style:t},(t,e)=>{}).gt().lt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Template Wizard"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.wt(e),e.ontouchend=()=>{e.click()}}).D().H().D().lt({class:"bm-n",textContent:"✖","aria-label":'Close window "Template Wizard"'},(t,e)=>{e.onclick=()=>{document.querySelector(`#${this.vt}`)?.remove()},e.ontouchend=()=>{e.click()}}).D().D().H({class:"bm-h"}).H({class:"bm-E bm-d"}).W(1,{textContent:"Template Wizard"}).D().D().F().D().H({class:"bm-E"}).W(2,{textContent:"Status"}).D().O({id:"bm-o",textContent:"Loading template storage status..."}).D().D().H({class:"bm-E bm-A"}).W(2,{textContent:"Detected templates:"}).D().D().D().D().L(this.Mt),this.xt(`#${this.vt}.bm-N`,`#${this.vt} .bm-L`),i(this,F,V).call(this),i(this,F,z).call(this)}};F=new WeakSet,V=function(){const t=this.schemaVersion.split(/[-\.\+]/),e=this.xe.split(/[-\.\+]/);let n="";t[0]==e[0]?t[1]==e[1]?(n='Template storage health: Healthy!
No futher action required. (Reason: Semantic version matches)',this.we="Good"):(n='Template storage health: Poor!
You can still use your template, but some features may not work. It is recommended that you update Blue Marble\'s template storage. (Reason: MINOR version mismatch)',this.we="Poor"):t[0]Bad!
It is guaranteed that some features are broken. You might still be able to use the template. It is HIGHLY recommended that you download all templates and update Blue Marble\'s template storage before continuing. (Reason: MAJOR version mismatch)',this.we="Bad"):(n='Template storage health: Dead!
Blue Marble can not load the template storage. (Reason: MAJOR version unknown)',this.we="Dead");const s=`
If you want to continue using your current templates, then make sure the template storage (schema) is up-to-date.
If you don't want to update the template storage, then downgrade Blue Marble to version ${r(this.scriptVersion)} to continue using your templates.
Alternatively, if you don't care about corrupting the templates listed below, you can fix any issues with the template storage by uploading a new template.`,o=function(){const t=[...document.querySelectorAll("body > div > .hidden")].filter(t=>/version:/i.test(t.textContent));if(t[0]){const e=t[0].textContent?.match(/\d+/);return e?new Date(Number(e[0])):void 0}}();let l=o?a(o):"???";this.ht("#bm-o",`${n}
Your templates were created during Blue Marble version ${r(this.scriptVersion)} with schema version ${r(this.schemaVersion)}.
The current Blue Marble version is ${r(this.version)} and requires schema version ${r(this.xe)}.
Wplace was last updated on ${l}.${"Good"!=this.we?s:""}`);const c=new M(this.name,this.version);"Dead"!=this.we&&(c.H({class:"bm-E bm-x bm-d",style:"gap: 1.5ch;"}),c.lt({textContent:"Download all templates"},(t,e)=>{e.onclick=()=>{e.disabled=!0,this.qt.ye().then(()=>{e.disabled=!1})}}).D()),"Poor"!=this.we&&"Bad"!=this.we||c.lt({textContent:`Update template storage to ${this.xe}`},(t,e)=>{e.onclick=()=>{e.disabled=!0,i(this,F,_).call(this,!0)}}).D(),c.D().L(document.querySelector("#bm-o").parentNode)},z=function(){const t=this.fe?.templates;if(Object.keys(t).length>0){const e=document.querySelector(`#${this.vt} .bm-A`),i=new M(this.name,this.version);i.H({id:"bm-r",class:"bm-E"});for(const e in t){const n=e,o=t[e];if(t.hasOwnProperty(e)){const t=n.split(" "),e=Number(t?.[0]),a=d(t?.[1]||"0",this.qt.$e),r=o.name||`Template ${e||""}`,l=o?.coords?.split(",").map(Number),c=o.pixels?.total??void 0,h=void 0,m="number"==typeof e?s(e):"???",u="number"==typeof a?s(a):"???",b="number"==typeof c?s(c):"???";i.H({class:"bm-E bm-x"}).H({class:"bm-x",style:"flex-direction: column; gap: 0;"}).H({class:"bm-1",textContent:h||"🖼️"}).D().N({textContent:`#${m}`}).D().D().H({class:"bm-x bm-0"}).W(3,{textContent:r}).D().B({textContent:`Uploaded by user #${u}`}).D().B({textContent:`Coordinates: ${l.join(", ")}`}).D().B({textContent:`Total Pixels: ${b}`}).D().D().D()}}i.D().L(e)}},_=async function(t){if(t){const t=document.querySelector(`#${this.vt} .bm-h`);t.innerHTML="",new M(this.name,this.version).H({class:"bm-E"}).H({class:"bm-E bm-d"}).W(1,{textContent:"Template Wizard"}).D().D().F().D().H({class:"bm-E"}).W(2,{textContent:"Status"}).D().O({textContent:"Updating template storage. Please wait..."}).D().D().D().L(t)}GM_deleteValue("bmCoords");const e=this.fe?.templates;if(Object.keys(e).length>0)for(const[t,i]of Object.entries(e))if(e.hasOwnProperty(t)){const t=new H({displayName:i.name,Ht:i.tiles});t.Rt();const e=await this.qt.ve(t);await this.qt.Me(e,t.displayName,t.coords)}t&&(console.log("Restarting Template Wizard..."),document.querySelector(`#${this.vt}`).remove(),new R(this.name,this.version,this.xe,this.qt).Ct())};var j,E,Y,J,X,q,Z,Q,K,tt=R;j=new WeakSet,E=function(){new G(this).Ct()},Y=async function(t,e,i){i.preventDefault();const n=await async function(t){let e="";return t&&(e=t.clipboardData.getData("text/plain")),0!=e.length||(await navigator.clipboard.readText().then(t=>{e=t}).catch(t=>{l("Failed to retrieve clipboard data using navigator! Using fallback methods...")}),0!=e.length||(e=window.clipboardData?.getData("Text"))),e}(i),s=n.split(/[^a-zA-Z0-9]+/).filter(t=>t).map(Number).filter(t=>!isNaN(t));2==s.length&&"bm-H"==e.id?(t.ht("bm-H",s?.[0]||""),t.ht("bm-I",s?.[1]||"")):1==s.length?t.ht(e.id,s?.[0]||""):(t.ht("bm-J",s?.[0]||""),t.ht("bm-K",s?.[1]||""),t.ht("bm-H",s?.[2]||""),t.ht("bm-I",s?.[3]||""))},J=new WeakSet,X=async function(){GM.setValue("bmTemplates",JSON.stringify(this.Ce))},q=async function(t){console.log("Parsing BlueMarble...");const e=t.templates;console.log(`BlueMarble length: ${Object.keys(e).length}`);const i=t?.schemaVersion,n=i.split(/[-\.\+]/),s=this.schemaVersion.split(/[-\.\+]/),o=t?.scriptVersion;console.log(`BlueMarble Template Schema: ${i}; Script Version: ${o}`),n[0]==s[0]?(n[1]!=s[1]&&new tt(this.name,this.version,this.schemaVersion,this).Ct(),this.ge=await async function({Nt:t,Te:i,ge:n}){if(Object.keys(e).length>0)for(const s in e){const o=s,a=e[s];if(console.log(`Template Key: ${o}`),e.hasOwnProperty(s)){const e=o.split(" "),s=Number(e?.[0]),r=e?.[1]||"0",l=a.name||`Template ${s||""}`,c={total:a.pixels?.total,colors:new Map(Object.entries(a.pixels?.colors||{}).map(([t,e])=>[Number(t),e]))},h=a.tiles,m={},d={},u=t*i;for(const t in h)if(console.log(t),h.hasOwnProperty(t)){const e=b(h[t]),i=new Blob([e],{type:"image/png"}),n=await createImageBitmap(i);m[t]=n;const s=new OffscreenCanvas(u,u).getContext("2d");s.drawImage(n,0,0);const o=s.getImageData(0,0,n.width,n.height);d[t]=new Uint32Array(o.data.buffer)}const p=new H({displayName:l,Dt:s||this.ge?.length||0,Lt:r||""});p.Bt=c,p.Ht=m,p.Ot=d,n.push(p),console.log(this.ge),console.log("^^^ This ^^^")}}return n}({Nt:this.Nt,Te:this.Te,ge:this.ge})):n[0]>>24&255,y=f>>>24&255,$=b.get(w)??-2,v=b.get(f)??-2;if(this.pe.get($)&&(e[i*c+h]=f),-1==$){const t=536870912;this.pe.get($)?e[i*c+h]=0:(u/o&1)==(g/o&1)?(e[i*c+h]=t,e[(i-1)*c+(h-1)]=t,e[(i-1)*c+(h+1)]=t,e[(i+1)*c+(h-1)]=t,e[(i+1)*c+(h+1)]=t):(e[i*c+h]=0,e[(i-1)*c+h]=t,e[(i+1)*c+h]=t,e[i*c+(h-1)]=t,e[i*c+(h+1)]=t)}if(!s&&x>m&&v!=$&&(d||y>m)){const t=e[i*c+h];for(const s of n){const[n,o,a]=s,r=0!=n?1!=n?t:4278190335:0;e[(i+a)*c+(h+o)]=r}}if(-1==$&&f<=m){const t=p.get($);p.set($,t?t+1:1);continue}if(x<=m||y<=m)continue;if(v!=$)continue;const M=p.get($);p.set($,M?M+1:1)}return console.log("List of template pixels that match the tile:"),console.log(p),{Be:p,Ie:e}},Q=new WeakSet,K=function(t){const e=JSON.parse(GM_getValue("bmUserSettings","{}"));e.telemetry=t,GM.setValue("bmUserSettings",JSON.stringify(e))};var et=GM_info.script.name.toString(),it=GM_info.script.version.toString();!function(t){const e=document.createElement("script");e.setAttribute("bm-S",et),e.setAttribute("bm-O","color: cornflowerblue;"),e.textContent=`(${t})();`,document.documentElement?.appendChild(e),e.remove()}(()=>{const t=document.currentScript,e=t?.getAttribute("bm-S")||"Blue Marble",i=t?.getAttribute("bm-O")||"",n=new Map;window.addEventListener("message",t=>{const{source:s,endpoint:o,blobID:a,blobData:r,blink:l}=t.data,c=Date.now()-l;if(console.groupCollapsed(`%c${e}%c: ${n.size} Recieved IMAGE message about blob "${a}"`,i,""),console.log(`Blob fetch took %c${String(Math.floor(c/6e4)).padStart(2,"0")}:${String(Math.floor(c/1e3)%60).padStart(2,"0")}.${String(c%1e3).padStart(3,"0")}%c MM:SS.mmm`,i,""),console.log(n),console.groupEnd(),"blue-marble"==s&&a&&r&&!o){const t=n.get(a);"function"==typeof t?t(r):h(`%c${e}%c: Attempted to retrieve a blob (%s) from queue, but the blobID was not a function! Skipping...`,i,"",a),n.delete(a)}});const s=window.fetch;window.fetch=async function(...t){const o=await s.apply(this,t),a=o.clone(),r=(t[0]instanceof Request?t[0]?.url:t[0])||"ignore",l=a.headers.get("content-type")||"";if(l.includes("application/json"))console.log(`%c${e}%c: Sending JSON message about endpoint "${r}"`,i,""),a.json().then(t=>{window.postMessage({source:"blue-marble",endpoint:r,jsonData:t},"*")}).catch(t=>{console.error(`%c${e}%c: Failed to parse JSON: `,i,"",t)});else if(l.includes("image/")&&!r.includes("openfreemap")&&!r.includes("maps")){const t=Date.now(),s=await a.blob();return console.log(`%c${e}%c: ${n.size} Sending IMAGE message about endpoint "${r}"`,i,""),new Promise(o=>{const l=crypto.randomUUID();n.set(l,t=>{o(new Response(t,{headers:a.headers,status:a.status,statusText:a.statusText})),console.log(`%c${e}%c: ${n.size} Processed blob "${l}"`,i,"")}),window.postMessage({source:"blue-marble",endpoint:r,blobID:l,blobData:s,blink:t})}).catch(s=>{const o=Date.now();console.error(`%c${e}%c: Failed to Promise blob!`,i,""),console.groupCollapsed(`%c${e}%c: Details of failed blob Promise:`,i,""),console.log(`Endpoint: ${r}\nThere are ${n.size} blobs processing...\nBlink: ${t.toLocaleString()}\nTime Since Blink: ${String(Math.floor(o/6e4)).padStart(2,"0")}:${String(Math.floor(o/1e3)%60).padStart(2,"0")}.${String(o%1e3).padStart(3,"0")} MM:SS.mmm`),console.error("Exception stack:",s),console.groupEnd()})}return o}});var nt=GM_getResourceText("CSS-BM-File");GM_addStyle(nt);var st,ot="robotoMonoInjectionPoint";ot.indexOf("@font-face")+1?(console.log("Loading Roboto Mono as a file..."),GM_addStyle(ot)):((st=document.createElement("link")).href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap",st.rel="preload",st.as="style",st.onload=function(){this.onload=null,this.rel="stylesheet"},document.head?.appendChild(st));var at=JSON.parse(GM_getValue("bmUserSettings","{}")),rt=(new class{constructor(){this.Pe=null,this.Ae=null,this.We="#bm-j"}Fe(t){return this.Ae=t,this.Pe=new MutationObserver(t=>{for(const e of t)for(const t of e.addedNodes)t instanceof HTMLElement&&t.matches?.(this.We)}),this}Ve(){return this.Pe}observe(t,e=!1,i=!1){t.observe(this.Ae,{childList:e,subtree:i})}},new class extends M{constructor(t,i){super(t,i),e(this,j),this.window=null,this.vt="bm-w",this.Mt=document.body}Ct(){document.querySelector(`#${this.vt}`)?this.yt("Main window already exists!"):(this.window=this.H({id:this.vt,class:"bm-N bm-G",style:"top: 10px; left: unset; right: 75px;"},(t,e)=>{}).gt().lt({class:"bm-n",textContent:"▼","aria-label":'Minimize window "Blue Marble"',"data-button-status":"expanded"},(t,e)=>{e.onclick=()=>t.wt(e),e.ontouchend=()=>{e.click()}}).D().H().D().D().H({class:"bm-h"}).H({class:"bm-E"}).A({class:"bm-M",src:"https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png"},(t,e)=>{const i=new Date;204==Math.floor((i.getTime()-new Date(i.getFullYear(),0,1))/864e5)+1&&(e.parentNode.style.position="relative",e.parentNode.innerHTML=e.parentNode.innerHTML+'',e.onload=()=>{(new O).Jt(document.querySelector(`#${this.vt}`))})}).D().W(1,{textContent:this.name}).D().D().F().D().H({class:"bm-E"}).B({id:"bm-q",textContent:"Droplets:"}).D().V().D().B({id:"bm-k",textContent:"Next level in..."}).D().V().D().B({textContent:"Charges: "}).ft(Date.now(),1e3,{style:"font-weight: 700;"},(t,e)=>{t.p.ze=e.id}).D().D().D().F().D().H({class:"bm-E"}).H({class:"bm-E"}).lt({class:"bm-n bm-C",style:"margin-top: 0;",innerHTML:''},(t,e)=>{e.onclick=()=>{const e=t.p?._e;e?.[0]?(t.ht("bm-J",e?.[0]||""),t.ht("bm-K",e?.[1]||""),t.ht("bm-H",e?.[2]||""),t.ht("bm-I",e?.[3]||"")):t.yt("Coordinates are malformed! Did you try clicking on the canvas first?")}}).D().dt({type:"number",id:"bm-J",class:"bm-v",placeholder:"Tl X",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,j,Y).call(this,t,e,n))}).D().dt({type:"number",id:"bm-K",class:"bm-v",placeholder:"Tl Y",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,j,Y).call(this,t,e,n))}).D().dt({type:"number",id:"bm-H",class:"bm-v",placeholder:"Px X",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,j,Y).call(this,t,e,n))}).D().dt({type:"number",id:"bm-I",class:"bm-v",placeholder:"Px Y",min:0,max:2047,step:1,required:!0},(t,e)=>{e.addEventListener("paste",n=>i(this,j,Y).call(this,t,e,n))}).D().D().H({class:"bm-E"}).ut({class:"bm-D",textContent:"Upload Template",accept:"image/png, image/jpeg, image/webp, image/bmp, image/gif"}).D().D().H({class:"bm-E bm-s"}).lt({textContent:"Disable","data-button-status":"shown"},(t,e)=>{e.onclick=()=>{e.disabled=!0,"shown"==e.dataset.buttonStatus?(t.p?.qt?.Ue(!1),e.dataset.buttonStatus="hidden",e.textContent="Enable",t.$t("Disabled templates!")):(t.p?.qt?.Ue(!0),e.dataset.buttonStatus="shown",e.textContent="Disable",t.$t("Enabled templates!")),e.disabled=!1}}).D().lt({textContent:"Create"},(t,e)=>{e.onclick=()=>{const e=document.querySelector(`#${this.vt} .bm-D`),i=document.querySelector("#bm-J");if(!i.checkValidity())return i.reportValidity(),void t.yt("Coordinates are malformed! Did you try clicking on the canvas first?");const n=document.querySelector("#bm-K");if(!n.checkValidity())return n.reportValidity(),void t.yt("Coordinates are malformed! Did you try clicking on the canvas first?");const s=document.querySelector("#bm-H");if(!s.checkValidity())return s.reportValidity(),void t.yt("Coordinates are malformed! Did you try clicking on the canvas first?");const o=document.querySelector("#bm-I");if(!o.checkValidity())return o.reportValidity(),void t.yt("Coordinates are malformed! Did you try clicking on the canvas first?");e?.files[0]?(t?.p?.qt.Me(e.files[0],e.files[0]?.name.replace(/\.[^/.]+$/,""),[Number(i.value),Number(n.value),Number(s.value),Number(o.value)]),t.$t("Drew to canvas!")):t.yt("No file selected!")}}).D().lt({textContent:"Filter"},(t,e)=>{e.onclick=()=>i(this,j,E).call(this)}).D().D().H({class:"bm-E"}).bt({id:this.v,placeholder:`Status: Sleeping...\nVersion: ${this.version}`,readOnly:!0}).D().D().H({class:"bm-E bm-s",style:"margin-bottom: 0; flex-direction: column;"}).H({class:"bm-s"}).lt({class:"bm-n",innerHTML:"⚙️",title:"Settings"},(t,e)=>{e.onclick=()=>{t.$.Ct()}}).D().lt({class:"bm-n",innerHTML:"🧙",title:"Template Wizard"},(t,e)=>{e.onclick=()=>{const e=t.p?.qt;new tt(this.name,this.version,e?.schemaVersion,e).Ct()}}).D().lt({class:"bm-n",innerHTML:"🎨",title:"Template Color Converter"},(t,e)=>{e.onclick=()=>{window.open("https://pepoafonso.github.io/color_converter_wplace/","_blank","noopener noreferrer")}}).D().lt({class:"bm-n",innerHTML:"🌐",title:"Official Blue Marble Website"},(t,e)=>{e.onclick=()=>{window.open("https://bluemarble.lol/","_blank","noopener noreferrer")}}).D().lt({class:"bm-n",title:"Donate to SwingTheVine",innerHTML:''},(t,e)=>{e.onclick=()=>{window.open("https://ko-fi.com/swingthevine","_blank","noopener noreferrer")}}).D().lt({class:"bm-n",innerHTML:"🤝",title:"Credits"},(t,e)=>{e.onclick=()=>{new U(this.name,this.version).Ct()}}).D().D().N({textContent:"Made by SwingTheVine",style:"margin-top: auto;"}).D().D().D().D().D().L(this.Mt),this.xt(`#${this.vt}.bm-N`,`#${this.vt} .bm-L`))}}(et,it)),lt=new class{constructor(t,i){e(this,J),this.name=t,this.version=i,this.Se=null,this.$=null,this.schemaVersion="2.0.0",this.Ge=null,this.$e="!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",this.Nt=1e3,this.Te=3,this.Ne=3,this.Kt=function(t){const e=v;e.unshift({id:-1,premium:!1,name:"Erased",rgb:[222,250,206]}),e.unshift({id:-2,premium:!1,name:"Other",rgb:[0,0,0]});const i=new Map;for(const n of e){if(0==n.id||-2==n.id)continue;const e=n.rgb[0],s=n.rgb[1],o=n.rgb[2];for(let a=-t;a<=t;a++)for(let r=-t;r<=t;r++)for(let l=-t;l<=t;l++){const t=e+a,c=s+r,h=o+l;if(t<0||t>255||c<0||c>255||h<0||h>255)continue;const m=(255<<24|h<<16|c<<8|t)>>>0;i.has(m)||i.set(m,n.id)}}return{palette:e,jt:i}}(this.Ne),this.De=null,this.Re="",this.ge=[],this.Ce=null,this.je=!0,this.Ee=null,this.pe=new Map}Ye(t){this.Se=t}k(t){this.$=t}async Je(){return{whoami:this.name.replace(" ",""),scriptVersion:this.version,schemaVersion:this.schemaVersion,templates:{}}}async Me(t,e,n){this.Ce||(this.Ce=await this.Je(),console.log("Creating JSON...")),this.Se.$t(`Creating template at ${n.join(", ")}...`);const s=new H({displayName:e,Dt:0,Lt:m(this.Ge||0,this.$e),file:t,coords:n}),o=!this.$?.kt?.flags?.includes("hl-noSkip"),a=this.$?.kt?.flags?.includes("hl-agSkip");console.log(`Should Skip: ${o}; Should Agg Skip: ${a}`);const{Ut:r,Gt:l}=await s.At(this.Nt,this.Kt,o,a);s.Ht=r;const c={total:s.Bt.total,colors:Object.fromEntries(s.Bt.colors)};this.Ce.templates[`${s.Dt} ${s.Lt}`]={name:s.displayName,coords:n.join(", "),enabled:!0,pixels:c,tiles:l},this.ge=[],this.ge.push(s),this.Se.$t(`Template created at ${n.join(", ")}!`),console.log(Object.keys(this.Ce.templates).length),console.log(this.Ce),console.log(this.ge),console.log(JSON.stringify(this.Ce)),await i(this,J,X).call(this)}Xe(){}async qe(){this.Ce||(this.Ce=await this.Je(),console.log("Creating JSON..."))}async Ze(){l("Downloading all templates..."),console.log(this.ge);for(const t of this.ge)await this.Qe(t),await n(500)}async ye(){const t=JSON.parse(GM_getValue("bmTemplates","{}"))?.templates;if(console.log(t),Object.keys(t).length>0)for(const[e,i]of Object.entries(t))t.hasOwnProperty(e)&&(await this.Qe(new H({displayName:i.name,Dt:e.split(" ")?.[0],Lt:e.split(" ")?.[1],Ht:i.tiles})),await n(500))}async Qe(t){t.Rt();const e=`${t.coords.join("-")}_${t.displayName.replaceAll(" ","-")}`,i=await this.ve(t);await GM.download({url:URL.createObjectURL(i),name:e+".png",Ke:"uniquify",onload:()=>{l(`Download of template '${e}' complete!`)},onerror:(t,i)=>{c(`Download of template '${e}' failed because ${t}! Details: ${i}`)},ontimeout:()=>{h(`Download of template '${e}' has timed out!`)}})}async ve(t){console.log(t);const e=t.Ht,i=Object.keys(e).sort(),n=await Promise.all(i.map(t=>{return i=e[t],new Promise((t,e)=>{const n=new Image;n.onload=()=>t(n),n.onerror=e,n.src="data:image/png;base64,"+i});var i}));let s=1/0,o=1/0,a=0,r=0;i.forEach((t,e)=>{const[i,l,c,h]=t.split(",").map(Number),m=n[e],d=i*this.Nt+c,u=l*this.Nt+h;s=Math.min(s,d),o=Math.min(o,u),a=Math.max(a,d+m.width/this.Te),r=Math.max(r,u+m.height/this.Te)}),console.log(`Absolute coordinates: (${s}, ${o}) and (${a}, ${r})`);const l=a-s,c=r-o,h=l*this.Te,m=c*this.Te;console.log(`Template Width: ${l}\nTemplate Height: ${c}\nCanvas Width: ${h}\nCanvas Height: ${m}`);const d=new OffscreenCanvas(h,m),u=d.getContext("2d");i.forEach((t,e)=>{const[i,a,r,l]=t.split(",").map(Number),c=n[e],h=i*this.Nt+r,m=a*this.Nt+l;console.log(`Drawing tile (${i}, ${a}, ${r}, ${l}) (${h}, ${m}) at (${h-s}, ${m-o}) on the canvas...`),u.drawImage(c,(h-s)*this.Te,(m-o)*this.Te,c.width,c.height)}),u.globalCompositeOperation="destination-over",u.drawImage(d,0,-1),u.drawImage(d,0,1),u.drawImage(d,-1,0),u.drawImage(d,1,0);const b=new OffscreenCanvas(l,c),p=b.getContext("2d");return p.imageSmoothingEnabled=!1,p.drawImage(d,0,0,l*this.Te,c*this.Te,0,0,l,c),b.convertToBlob({type:"image/png"})}async ti(t,e){if(!this.je)return t;const n=this.Nt*this.Te;e=e[0].toString().padStart(4,"0")+","+e[1].toString().padStart(4,"0"),console.log(`Searching for templates in tile: "${e}"`);const o=this.ge;console.log(o),o.sort((t,e)=>t.Dt-e.Dt),console.log(o);const a=o.map(t=>{const i=Object.keys(t.Ht).filter(t=>t.startsWith(e));if(0===i.length)return null;const n=i.map(e=>{const i=e.split(",");return{ei:t,Ft:t.Ht[e],Ot:t.Ot?.[e],ii:[i[0],i[1]],ni:[i[2],i[3]]}});return n?.[0]}).filter(Boolean);console.log(a);const r=a?.length||0;if(console.log(`templateCount = ${r}`),!(r>0))return this.Se.$t(`Sleeping\nVersion: ${this.version}`),t;{const t=s(o.filter(t=>Object.keys(t.Ht).filter(t=>t.startsWith(e)).length>0).reduce((t,e)=>t+(e.Bt.total||0),0));this.Se.$t(`Displaying ${r} template${1==r?"":"s"}.\nTotal pixels: ${t}`)}const l=await createImageBitmap(t),c=new OffscreenCanvas(n,n),h=c.getContext("2d");h.imageSmoothingEnabled=!1,h.beginPath(),h.rect(0,0,n,n),h.clip(),h.clearRect(0,0,n,n),h.drawImage(l,0,0,n,n);const m=h.getImageData(0,0,n,n),d=new Uint32Array(m.data.buffer),u=this.$?.kt?.highlight||[[2,0,0]],b=u?.[0],p=1==u?.length&&2==b?.[0]&&0==b?.[1]&&0==b?.[2];for(const t of a){console.log("Template:"),console.log(t);const n=!!t.ei.Bt?.colors?.get(-1);let s=t.Ot.slice();const o=Number(t.ni[0])*this.Te,a=Number(t.ni[1])*this.Te;if(0!=this.pe.size||n||h.drawImage(t.Ft,o,a),!s){const e=h.getImageData(o,a,t.Ft.width,t.Ft.height);s=new Uint32Array(e.data.buffer)}const r=Date.now(),{Be:l,Ie:c}=i(this,J,Z).call(this,{ke:d,De:s,Le:[o,a,t.Ft.width,t.Ft.height],He:u,Oe:p});let m=0;const b=0;for(const[t,e]of l)t!=b&&(m+=e);0==this.pe.size&&!n&&p||(console.log("Colors to filter: ",this.pe),h.drawImage(await createImageBitmap(new ImageData(new Uint8ClampedArray(c.buffer),t.Ft.width,t.Ft.height)),o,a)),console.log(`Finished calculating correct pixels & filtering colors for the tile ${e} in ${(Date.now()-r)/1e3} seconds!\nThere are ${m} correct pixels.`),void 0===t.ei.Bt.correct&&(t.ei.Bt.correct={}),t.ei.Bt.correct[e]=l}return await c.convertToBlob({type:"image/png"})}si(t){console.log("Importing JSON..."),console.log(t),"BlueMarble"==t?.whoami&&i(this,J,q).call(this,t)}Ue(t){this.je=t}}(et,it),ct=new class{constructor(t){this.qt=t,this.oi=!1,this.ze="",this._e=[],this.ai=[]}ri(t){window.addEventListener("message",async e=>{const i=e.data,n=i.jsonData;if(!i||"blue-marble"!==i.source)return;if(!i.endpoint)return;const o=i.endpoint?.split("?")[0].split("/").filter(t=>t&&isNaN(Number(t))).filter(t=>t&&!t.includes(".")).pop();switch(console.log('%cBlue Marble%c: Recieved message about "%s"',"color: cornflowerblue;","",o),o){case"me":if(n.status&&"2"!=n.status?.toString()[0])return void t.yt("You are not logged in or Wplace is offline!\nCould not fetch userdata.");const e=Math.ceil(Math.pow(Math.floor(n.level)*Math.pow(30,.65),1/.65)-n.pixelsPainted);if(console.log(n.id),(n.id||0===n.id)&&console.log(m(n.id,"!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~")),this.qt.Ge=n.id,0!=this.ze.length){const t=document.querySelector("#"+this.ze);if(t){const e=n.charges;t.dataset.endDate=Date.now()+(e.max-e.count)*e.cooldownMs}}t.ht("bm-q",`Droplets: ${s(n.droplets)}`),t.ht("bm-k",`Next level in ${s(e)} pixel${1==e?"":"s"}`);break;case"pixel":const o=i.endpoint.split("?")[0].split("/").filter(t=>t&&!isNaN(Number(t))),l=new URLSearchParams(i.endpoint.split("?")[1]),c=[l.get("x"),l.get("y")];if(this._e.length&&(!o.length||!c.length))return void t.yt("Coordinates are malformed!\nDid you try clicking the canvas first?");this._e=[...o,...c];const h=(a=o,r=c,[parseInt(a[0])%4*1e3+parseInt(r[0]),parseInt(a[1])%4*1e3+parseInt(r[1])]),d=document.querySelectorAll("span");for(const t of d)if(t.textContent.trim().includes(`${h[0]}, ${h[1]}`)){let e=document.querySelector("#bm-j");const i=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${c[0]}, Px Y: ${c[1]})`;e?e.textContent=i:(e=document.createElement("span"),e.id="bm-j",e.textContent=i,e.style="margin-left: calc(var(--spacing)*3); font-size: small;",t.parentNode.parentNode.insertAdjacentElement("afterend",e))}break;case"tile":case"tiles":let u=i.endpoint.split("/");u=[parseInt(u[u.length-2]),parseInt(u[u.length-1].replace(".png",""))];const b=i.blobID,p=i.blobData,g=Date.now(),f=await this.qt.ti(p,u);console.log(`Finished loading the tile in ${(Date.now()-g)/1e3} seconds!`),window.postMessage({source:"blue-marble",blobID:b,blobData:f,blink:i.blink});break;case"robots":this.oi="false"==n.userscript?.toString().toLowerCase();break}var a,r})}async li(t){console.log("Sending heartbeat to telemetry server...");let e=GM_getValue("bmUserSettings","{}");if(e=JSON.parse(e),!e||!e.telemetry||!e.uuid)return void console.log("Telemetry is disabled, not sending heartbeat.");const i=navigator.userAgent;let n=await this.ci(i),s=this.hi(i);GM_xmlhttpRequest({method:"POST",url:"https://telemetry.thebluecorner.net/heartbeat",headers:{"Content-Type":"application/json"},data:JSON.stringify({uuid:e.uuid,version:t,browser:n,os:s}),onload:t=>{200!==t.status&&c("Failed to send heartbeat:",t.statusText)},onerror:t=>{c("Error sending heartbeat:",t)}})}async ci(t=navigator.userAgent){return(t=t||"").includes("OPR/")||t.includes("Opera")?"Opera":t.includes("Edg/")?"Edge":t.includes("Vivaldi")?"Vivaldi":t.includes("YaBrowser")?"Yandex":t.includes("Kiwi")?"Kiwi":t.includes("Brave")?"Brave":t.includes("Firefox/")?"Firefox":t.includes("Chrome/")?"Chrome":t.includes("Safari/")?"Safari":navigator.brave&&"function"==typeof navigator.brave.isBrave&&await navigator.brave.isBrave()?"Brave":"Unknown"}hi(t=navigator.userAgent){return/Windows NT 11/i.test(t=t||"")?"Windows 11":/Windows NT 10/i.test(t)?"Windows 10":/Windows NT 6\.3/i.test(t)?"Windows 8.1":/Windows NT 6\.2/i.test(t)?"Windows 8":/Windows NT 6\.1/i.test(t)?"Windows 7":/Windows NT 6\.0/i.test(t)?"Windows Vista":/Windows NT 5\.1|Windows XP/i.test(t)?"Windows XP":/Mac OS X 10[_\.]15/i.test(t)?"macOS Catalina":/Mac OS X 10[_\.]14/i.test(t)?"macOS Mojave":/Mac OS X 10[_\.]13/i.test(t)?"macOS High Sierra":/Mac OS X 10[_\.]12/i.test(t)?"macOS Sierra":/Mac OS X 10[_\.]11/i.test(t)?"OS X El Capitan":/Mac OS X 10[_\.]10/i.test(t)?"OS X Yosemite":/Mac OS X 10[_\.]/i.test(t)?"macOS":/Android/i.test(t)?"Android":/iPhone|iPad|iPod/i.test(t)?"iOS":/Linux/i.test(t)?"Linux":"Unknown"}}(lt),ht=new class extends L{constructor(t,i,n){var s;super(t,i),e(this,C),this.kt=n,(s=this.kt).flags??(s.flags=[]),this.mi=structuredClone(this.kt),this.di="bmUserSettings",this.ui=5e3,this.bi=0,setInterval(this.pi.bind(this),this.ui)}async pi(){const t=JSON.stringify(this.kt);t!=JSON.stringify(this.mi)&&Date.now()-this.bi>this.ui&&(await GM.setValue(this.di,t),this.mi=structuredClone(this.kt),this.bi=Date.now(),console.log(t))}gi(t,e=void 0){const i=this.kt?.flags?.indexOf(t)??-1;-1!=i&&!0!==e?this.kt?.flags?.splice(i,1):-1==i&&!1!==e&&this.kt?.flags?.push(t)}Tt(){const t='',e='',n=this.kt?.highlight??[[1,0,1],[2,0,0],[1,-1,0],[1,1,0],[1,0,-1]];this.window=this.H({class:"bm-E"}).W(2,{textContent:"Pixel Highlight"}).D().F().D().H({class:"bm-E",style:"margin-left: 1.5ch;"}).R({textContent:"Highlight transparent pixels"},(t,e,i)=>{i.checked=!this.kt?.flags?.includes("hl-noTrans"),i.onchange=t=>this.gi("hl-noTrans",!t.target.checked)}).D().O({id:"bm-Z",textContent:"Choose a preset:",style:"font-weight: 700;"}).D().H({class:"bm-x",role:"group","aria-labelledby":"bm-Z"}).H({class:"bm-Y"}).B({textContent:"None"}).D().lt({innerHTML:t,"aria-label":'Preset "None"'},(t,e)=>{e.onclick=()=>i(this,C,S).call(this,"None")}).D().D().H({class:"bm-Y"}).B({textContent:"Cross"}).D().lt({innerHTML:e,"aria-label":'Preset "Cross Shape"'},(t,e)=>{e.onclick=()=>i(this,C,S).call(this,"Cross")}).D().D().H({class:"bm-Y"}).B({textContent:"X"}).D().lt({innerHTML:e.replace('d="M1,0H2V1H3V2H2V3H1V2H0V1H1Z"','d="M0,0V1H3V0H2V3H3V2H0V3H1V0Z"'),"aria-label":'Preset "X Shape"'},(t,e)=>{e.onclick=()=>i(this,C,S).call(this,"X")}).D().D().H({class:"bm-Y"}).B({textContent:"Full"}).D().lt({innerHTML:t.replace("#fff","#2f4f4f"),"aria-label":'Preset "Full Template"'},(t,e)=>{e.onclick=()=>i(this,C,S).call(this,"Full")}).D().D().D().O({id:"bm-14",textContent:"Create a custom pattern:",style:"font-weight: 700;"}).D().H({class:"bm-1g",role:"group","aria-labelledby":"bm-14"});for(let t=-1;t<=1;t++)for(let e=-1;e<=1;e++){const s=n[n.findIndex(([,i,n])=>i==e&&n==t)]?.[0]??0;let o="Disabled";1==s?o="Incorrect":2==s&&(o="Template"),this.window=this.lt({"data-status":o,"aria-label":`Sub-pixel ${o.toLowerCase()}`},(n,s)=>{s.onclick=()=>i(this,C,T).call(this,s,[e,t])}).D()}this.window=this.D().D().D()}St(){this.window=this.H({class:"bm-E"}).W(2,{textContent:"Pixel Highlight"}).D().F().D().H({class:"bm-E",style:"margin-left: 1.5ch;"}).R({textContent:"Template creation should skip transparent tiles"},(t,e,i)=>{i.checked=!this.kt?.flags?.includes("hl-noSkip"),i.onchange=t=>this.gi("hl-noSkip",!t.target.checked)}).D().R({innerHTML:"Experimental: Template creation should aggressively skip transparent tiles"},(t,e,i)=>{i.checked=this.kt?.flags?.includes("hl-agSkip"),i.onchange=t=>this.gi("hl-agSkip",t.target.checked)}).D().D().D()}}(et,it,at);rt.k(ht),rt.S(ct),lt.Ye(rt),lt.k(ht);var mt=JSON.parse(GM_getValue("bmTemplates","{}"));if(console.log(mt),lt.si(mt),console.log(at),console.log(Object.keys(at).length),0==Object.keys(at).length){const t=crypto.randomUUID();console.log(t),GM.setValue("bmUserSettings",JSON.stringify({uuid:t}))}setInterval(()=>ct.li(it),18e5);var dt=at?.telemetry;if(console.log(`Telemetry is ${!(null==dt)}`),null==dt||dt>1){const t=new class extends M{constructor(t,i,n,s){super(t,i),e(this,Q),this.window=null,this.vt="bm-g",this.Mt=document.body,this.fi=n,this.uuid=s}async Ct(){if(document.querySelector(`#${this.vt}`))return void this.yt("Telemetry window already exists!");const t=await this.p.ci(navigator.userAgent),e=this.p.hi(navigator.userAgent);this.window=this.H({id:this.vt,class:"bm-N",style:"height: 80vh; z-index: 9998;"}).H({class:"bm-h"}).H({class:"bm-E bm-d"}).W(1,{textContent:`${this.name} Telemetry`}).D().D().F().D().H({class:"bm-E bm-x",style:"gap: 1.5ch; flex-wrap: wrap;"}).lt({textContent:"Enable Telemetry"},(t,e)=>{e.onclick=()=>{i(this,Q,K).call(this,this.fi);const t=document.getElementById(this.vt);t?.remove()}}).D().lt({textContent:"Disable Telemetry"},(t,e)=>{e.onclick=()=>{i(this,Q,K).call(this,0);const t=document.getElementById(this.vt);t?.remove()}}).D().lt({textContent:"More Information"},(t,e)=>{e.onclick=()=>{window.open("https://github.com/SwingTheVine/Wplace-TelemetryServer#telemetry-data","_blank","noopener noreferrer")}}).D().D().H({class:"bm-E bm-A"}).H({class:"bm-E"}).W(2,{textContent:"Legal"}).D().O({textContent:`We collect anonymous telemetry data such as your browser, OS, and script version to make the experience better for everyone. The data is never shared personally. The data is never sold. You can turn this off by pressing the "Disable" button, but keeping it on helps us improve features and reliability faster. Thank you for supporting ${this.name}!`}).D().D().F().D().H({class:"bm-E"}).W(2,{textContent:"Non-Legal Summary"}).D().O({innerHTML:'You can disable telemetry by pressing the "Disable" button. If you would like to read more about what information we collect, press the "More Information" button.
This is the data stored on our servers:'}).D().X().Z({innerHTML:`A unique identifier (UUIDv4) generated by Blue Marble. This enables our telemetry to function without tracking your actual user ID.
Your UUID is: ${r(this.uuid)}`}).D().Z({innerHTML:`The version of Blue Marble you are using.
Your version is: ${r(this.version)}`}).D().Z({innerHTML:`Your browser type, which is used to determine Blue Marble outages and browser popularity.
Your browser type is: ${r(t)}`}).D().Z({innerHTML:`Your OS type, which is used to determine Blue Marble outages and OS popularity.
Your OS type is: ${r(e)}`}).D().Z({innerHTML:"The date and time that Blue Marble sent the telemetry information."}).D().D().O({innerHTML:'All of the data mentioned above is aggregated every hour. This means every hour, anything that could even remotly be considered "personal data" is deleted from our server. Here, "aggregated" data means things like "42 people used Blue Marble on Google Chrome this hour", which can\'t be used to identify anyone in particular.'}).D().D().D().D().D().L(this.Mt)}}(et,it,1,at?.uuid);t.S(ct),t.Ct()}rt.Ct(),ct.ri(rt),new MutationObserver((t,e)=>{const i=document.querySelector("#color-1");if(!i)return;let n=document.querySelector("#bm-z");if(!n){n=document.createElement("button"),n.id="bm-z",n.textContent="Move ↑",n.className="btn btn-soft",n.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 ↑"};const t=i.parentNode.parentNode.parentNode.parentNode.querySelector("h2");t.parentNode?.appendChild(n)}}).observe(document.body,{childList:!0,subtree:!0}),l(`%c${et}%c (${it}) userscript has loaded!`,"color: cornflowerblue;","")})(); \ No newline at end of file diff --git a/docs/CREDITS.md b/docs/CREDITS.md index c41ea49..d343680 100644 --- a/docs/CREDITS.md +++ b/docs/CREDITS.md @@ -39,6 +39,7 @@ Special Thanks: [Donators](https://ko-fi.com/swingthevine): * Espresso * BEST FAN +* FuchsDresden * Jack * raiken_au * Jacob diff --git a/docs/README.md b/docs/README.md index 2421ba8..12c728b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -51,7 +51,7 @@ Contact Me Blue Marble Website WakaTime -Total Patches +Total Patches Total Lines of Code Total Comments Compression diff --git a/package-lock.json b/package-lock.json index 8baaf9a..bb8c31c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wplace-bluemarble", - "version": "0.90.76", + "version": "0.91.102", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wplace-bluemarble", - "version": "0.90.76", + "version": "0.91.102", "devDependencies": { "esbuild": "^0.25.0", "jsdoc": "^4.0.5", diff --git a/package.json b/package.json index c1d47aa..d427e0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wplace-bluemarble", - "version": "0.91.0", + "version": "0.91.102", "type": "module", "homepage": "https://bluemarble.lol/", "repository": { diff --git a/src/BlueMarble.meta.js b/src/BlueMarble.meta.js index 52f4f98..4cf1d4e 100644 --- a/src/BlueMarble.meta.js +++ b/src/BlueMarble.meta.js @@ -2,7 +2,7 @@ // @name Blue Marble // @name:en Blue Marble // @namespace https://github.com/SwingTheVine/ -// @version 0.91.0 +// @version 0.91.102 // @description A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features. // @description:en A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features. // @author SwingTheVine diff --git a/src/Overlay.js b/src/Overlay.js index e3c5396..96ceb2a 100644 --- a/src/Overlay.js +++ b/src/Overlay.js @@ -37,6 +37,9 @@ export default class Overlay { /** The API manager instance. Later populated when setApiManager is called @type {ApiManager} */ this.apiManager = null; + + /** The Settings Manager instance. Later populated when setSettingsManager is called @type {SettingsManager} */ + this.settingsManager = null; this.outputStatusId = 'bm-output-status'; // ID for status element @@ -51,6 +54,12 @@ export default class Overlay { */ setApiManager(apiManager) {this.apiManager = apiManager;} + /** Populates the settingsManager variable with the settingsManager class. + * @param {SettingsManager} settingsManager - The settingsManager class instance + * @since 0.91.11 + */ + setSettingsManager(settingsManager) {this.settingsManager = settingsManager;} + /** Creates an element. * For **internal use** of the {@link Overlay} class. * @param {string} tag - The tag name as a string. @@ -109,10 +118,7 @@ export default class Overlay { ).join('') ] = value; } else if (property.startsWith('aria')) { - const camelCase = property.slice(5).split('-').map( - (part, i) => (i == 0) ? part : part[0].toUpperCase() + part.slice(1) - ).join(''); - element['aria' + camelCase[0].toUpperCase() + camelCase.slice(1)] = value; + element.setAttribute(property, value); // We can't do the solution for 'data', as 'aria-labelledby' would fail to apply } else { element[property] = value; } @@ -511,8 +517,24 @@ export default class Overlay { const properties = {'type': 'checkbox'}; // Shared checkbox DOM properties - const label = this.#createElement('label', {'textContent': additionalProperties['textContent'] ?? ''}); // Creates the label element - delete additionalProperties['textContent']; // Deletes 'textContent' DOM property before adding the properties to the checkbox + // Stores the label content from the additional property + const labelContent = {}; + + // If the label content was passed in as 'textContent'... + if (!!additionalProperties['textContent']) { + + // Store the information, then delete it from additionalProperties + labelContent['textContent'] = additionalProperties['textContent']; + delete additionalProperties['textContent']; // Deletes 'textContent' DOM property before adding the properties to the checkbox + } else if (!!additionalProperties['innerHTML']) { + // Else if the label content was passed in as 'innerHTML'... + + // Store the information, then delete it from additionalProperties + labelContent['innerHTML'] = additionalProperties['innerHTML']; + delete additionalProperties['textContent']; + } + + const label = this.#createElement('label', labelContent); // Creates the label element const checkbox = this.#createElement('input', properties, additionalProperties); // Creates the checkbox element label.insertBefore(checkbox, label.firstChild); // Makes the checkbox the first child of the label (before the text content) this.buildElement(); // Signifies that we are done adding children to the checkbox diff --git a/src/Template.js b/src/Template.js index 50f0d63..4fc7671 100644 --- a/src/Template.js +++ b/src/Template.js @@ -1,4 +1,4 @@ -import { uint8ToBase64 } from "./utils"; +import { sleep, uint8ToBase64, viewCanvasInNewTab } from "./utils"; /** An instance of a template. * Handles all mathematics, manipulation, and analysis regarding a single template. @@ -43,17 +43,26 @@ export default class Template { this.tileSize = tileSize; /** Total pixel count in template @type {{total: number, colors: Map, correct?: { [key: string]: Map }}} */ this.pixelCount = { total: 0, colors: new Map() }; + + this.shouldSkipTransTiles = true; // Should transparent template tiles be skipped during template creation? + this.shouldAggSkipTransTiles = false; // Should transparent template tiles be aggressively skipped during tempalte creation? } /** Creates chunks of the template for each tile. * @param {Number} tileSize - Size of the tile as determined by templateManager * @param {Object} paletteBM - An collection of Uint32Arrays containing the palette BM uses + * @param {boolean} shouldSkipTransTiles - Should transparent tiles be skipped over when creating the template? + * @param {boolean} shouldAggSkipTransTiles - Should transparent tiles be aggressively skipped over when creating the template? * @returns {Object} Collection of template bitmaps & buffers organized by tile coordinates * @since 0.65.4 */ - async createTemplateTiles(tileSize, paletteBM) { + async createTemplateTiles(tileSize, paletteBM, shouldSkipTransTiles, shouldAggSkipTransTiles) { console.log('Template coordinates:', this.coords); + // Updates the class instance variable with the new information + this.shouldSkipTransTiles = shouldSkipTransTiles; + this.shouldAggSkipTransTiles = shouldAggSkipTransTiles; + const shreadSize = 3; // Scale image factor for pixel art enhancement (must be odd) const bitmap = await createImageBitmap(this.file); // Create efficient bitmap from uploaded file const imageWidth = bitmap.width; @@ -64,8 +73,16 @@ export default class Template { const templateTiles = {}; // Holds the template tiles const templateTilesBuffers = {}; // Holds the buffers of the template tiles + // The main canvas used during template creation const canvas = new OffscreenCanvas(this.tileSize, this.tileSize); const context = canvas.getContext('2d', { willReadFrequently: true }); + + // The canvas used to check if a specific template tile is transparent or not + const transCanvas = new OffscreenCanvas(this.tileSize, this.tileSize); + const transContext = transCanvas.getContext('2d', { willReadFrequently: true }); + + // Makes it so that `.drawImage()` calls on the canvas used to calculate transparency always draw below what is already on the canvas + transContext.globalCompositeOperation = "destination-over"; // Prep the canvas for drawing the entire template (so we can find total pixels) canvas.width = imageWidth; @@ -101,7 +118,7 @@ export default class Template { contextMask.fillRect(1, 1, 1, 1); // For every tile... - for (let pixelY = this.coords[3]; pixelY < imageHeight + this.coords[3]; ) { + for (let pixelY = this.coords[3]; pixelY < imageHeight + this.coords[3];) { // Draws the partial tile first, if any // This calculates the size based on which is smaller: @@ -121,6 +138,26 @@ export default class Template { // B. The top left corner of the current tile to the bottom right corner of the image const drawSizeX = Math.min(this.tileSize - (pixelX % this.tileSize), imageWidth - (pixelX - this.coords[2])); + // If the user wants to skip any tiles where the template is transparent... + if (shouldSkipTransTiles) { + + // Detects if the canvas is fully transparent + const isTemplateTileTransparent = !this.calculateCanvasTransparency({ + bitmap: bitmap, + bitmapParams: [pixelX - this.coords[2], pixelY - this.coords[3], drawSizeX, drawSizeY], // Top left X, Top left Y, Width, Height + transCanvas: transCanvas, + transContext: transContext + }); + + console.log(`Tile contains template: ${!isTemplateTileTransparent}`); + + // If the template in this tile is transparent... + if (isTemplateTileTransparent) { + pixelX += drawSizeX; // If you remove this, it will get stuck forever processing the template + continue; // ...the user does not want to save this tile, so we skip to the next tile + } + } + console.log(`Math.min(${this.tileSize} - (${pixelX} % ${this.tileSize}), ${imageWidth} - (${pixelX - this.coords[2]}))`); console.log(`Draw Size X: ${drawSizeX}\nDraw Size Y: ${drawSizeY}`); @@ -155,6 +192,8 @@ export default class Template { context.globalCompositeOperation = "destination-in"; // The existing canvas content is kept where both the new shape and existing canvas content overlap. Everything else is made transparent. // For our purposes, this means any non-transparent pixels on the mask will be kept + console.log(`Should Skip: ${shouldSkipTransTiles}; Should Agg Skip: ${shouldAggSkipTransTiles}`); + // Fills the canvas with the mask context.fillStyle = context.createPattern(canvasMask, "repeat"); context.fillRect(0, 0, canvasWidth, canvasHeight); @@ -197,6 +236,110 @@ export default class Template { return { templateTiles, templateTilesBuffers }; } + /** Detects if the canvas is transparent. + * @param {Object} param - Object that contains the parameters for the function + * @param {ImageBitmap} param.bitmap - The bitmap template image + * @param {Array} param.bitmapParams - The parameters to obtain the template tile image from the bitmap + * @param {OffscreenCanvas | HTMLCanvasElement} param.transCanvas - The canvas to draw to in order to calculate this + * @param {OffscreenCanvasRenderingContext2D} param.transContext - The context for the transparent canvas to draw to + * @return {boolean} Is the canvas transparent? If transparent, then `true` is returned. Otherwise, `false`. + * @since 0.91.75 + */ + calculateCanvasTransparency({ + bitmap: bitmap, + bitmapParams: bitmapParams, + transCanvas: transCanvas, + transContext: transContext + }) { + + console.log(`Calculating template tile transparency...`); + + console.log(`Should Skip: ${this.shouldSkipTransTiles}; Should Agg: ${this.shouldAggSkipTransTiles}`); + + const timer = Date.now(); // Starts the timer + + // Contains the directions to move the canvas when duplicating, in the unit of pixels + const duplicationCoordinateArray = [ + [ 0, 1], // E.g. move 0 on the x axis, and 1 down on the y axis + [ 1, 0], + [ 0, -2], // E.g. move 0 on the x axis, and 2 up on the y axis + [ -2, 0], + [ 0, 4], + [ 4, 0], + [ 0, -8], + [ -8, 0], + [ 0, 16], + [ 16, 0], + [ 0, -32], + [-32, 0] + ]; + + // Changes the size of the canvas so that it equals the template tile + const transCanvasWidth = bitmapParams[2]; + const transCanvasHeight = bitmapParams[3]; + transCanvas.width = transCanvasWidth; + transCanvas.height = transCanvasHeight; + + transContext.clearRect(0, 0, transCanvasWidth, transCanvasHeight); // Clear any previous drawing (only runs when canvas size does not change) + + // If the user does want to aggressively skip transparent template tiles... + if (this.shouldAggSkipTransTiles) { + // (This code will only run if `this.shouldSkipTransTiles` is `true`) + + // Draw the template tile onto the canvas scaled down to 10x10 + transContext.drawImage( + bitmap, // The bitmap image + ...bitmapParams, // Bitmap image parameters (x, y, width, height) + 0, 0, // The coordinate draw the output *at* + 10, 10 // The width and height of the output + ); + } else { + // Else, the user wants to skip transparent template tiles normally... + + // Draw the template tile onto the canvas + transContext.drawImage( + bitmap, // The bitmap image + ...bitmapParams, // Bitmap image parameters (x, y, width, height) + 0, 0, // The coordinate draw the output *at* + transCanvasWidth, transCanvasHeight // Stretch to canvas (the canvas should already be the same size as the template image) + ) + + // For each canvas duplication... + for (const [relativeX, relativeY] of duplicationCoordinateArray) { + + // Duplicate the canvas onto itself, but shifted slightly + transContext.drawImage( + transCanvas, // The canvas we are drawing to *is* the source image + 0, 0, transCanvasWidth, transCanvasHeight, // The entire canvas (as a source image) + relativeX, relativeY, transCanvasWidth, transCanvasHeight // The output coordinates and size on the same canvas + ) + } + + // Scale down the image to 10x10, and store it between (0, 0) and (9, 9) on the canvas + transContext.drawImage( + transCanvas, // The canvas we are drawing to *is* the source image + 0, 0, transCanvasWidth, transCanvasHeight, // The entire canvas (as a source image) + 0, 0, 10, 10 // The output coordinates and size on the same canvas + ); + } + + const shunkCanvas = transContext.getImageData(0, 0, 10, 10); + const shunkCanvas32 = new Uint32Array(shunkCanvas.data.buffer); + + console.log(`Calculated canvas transparency in ${(Date.now() - timer) / 1000} seconds.`); + + // For every pixel in the `shrunkCanvas32` array... + for (const pixel of shunkCanvas32) { + + // If the pixel is NOT 100% transparent + if (!!pixel) { + return true; // Return `true` early since we confirmed a template exists in the tile + } + } + + return false; // Since we could not confirm any template exists, we assume no template eixsts in this tile + } + /** Calculates top left coordinate of template. * It uses `Template.chunked` to update `Template.coords` * @since 0.88.504 diff --git a/src/WindowCredits.js b/src/WindowCredits.js index ca405c3..30bfcd6 100644 --- a/src/WindowCredits.js +++ b/src/WindowCredits.js @@ -101,11 +101,12 @@ export default class WindowCredts extends Overlay { .addUl() .addLi({'textContent': 'Espresso'}).buildElement() .addLi({'textContent': 'BEST FAN'}).buildElement() + .addLi({'textContent': 'FuchsDresden'}).buildElement() .addLi({'textContent': 'Jack'}).buildElement() .addLi({'textContent': 'raiken_au'}).buildElement() .addLi({'textContent': 'Jacob'}).buildElement() .addLi({'textContent': 'StupidOne'}).buildElement() - .addLi({'textContent': '1 Anonymous Supporter'}).buildElement() + .addLi({'textContent': '2 Anonymous Supporters'}).buildElement() .buildElement() .buildElement() .buildElement() diff --git a/src/WindowFilter.css b/src/WindowFilter.css index c9a2356..bc5135f 100644 --- a/src/WindowFilter.css +++ b/src/WindowFilter.css @@ -17,7 +17,7 @@ } /* Filter color */ -.bm-filter-color { +#bm-window-filter .bm-filter-color { width: fit-content; max-width: 35ch; background-color: rgba(21, 48, 99, 0.9); @@ -28,13 +28,13 @@ } /* Filter color on hover */ -.bm-filter-color:hover, -.bm-filter-color:focus-within { +#bm-window-filter .bm-filter-color:hover, +#bm-window-filter.bm-filter-color:focus-within { background-color: rgba(17, 40, 85, 0.9); } /* Filter window container for RGB color display */ -.bm-filter-container-rgb { +#bm-window-filter .bm-filter-container-rgb { display: block; border: thick double darkslategray; width: fit-content; @@ -43,7 +43,7 @@ } /* Filter window container for RGB color display for Other color */ -.bm-filter-color[data-id="-2"] .bm-filter-container-rgb { +#bm-window-filter .bm-filter-color[data-id="-2"] .bm-filter-container-rgb { background: conic-gradient( #aa0000 0%, #aaaa00 16.6%, @@ -56,16 +56,16 @@ } /* Filter window container for RGB color display for Erased color */ -.bm-filter-color[data-id="-1"] .bm-filter-container-rgb { +#bm-window-filter .bm-filter-color[data-id="-1"] .bm-filter-container-rgb { background: url('data:image/svg+xml;utf8,') repeat; background-color: transparent !important; } -.bm-filter-color[data-id="-1"] .bm-filter-container-rgb svg { +#bm-window-filter .bm-filter-color[data-id="-1"] .bm-filter-container-rgb svg { fill: white !important; } /* Filter window container for RGB color display for Transparent color */ -.bm-filter-color[data-id="0"] .bm-filter-container-rgb { +#bm-window-filter .bm-filter-color[data-id="0"] .bm-filter-container-rgb { background-color: transparent !important; } @@ -75,19 +75,19 @@ } /* Filter window hide color button SVG */ -.bm-filter-container-rgb svg { +#bm-window-filter .bm-filter-container-rgb svg { width: 4ch; } /* Filter window container for color information */ -.bm-filter-color > .bm-flex-between { +#bm-window-filter .bm-filter-color > .bm-flex-between { flex-direction: column; align-items: flex-start; gap: 0; } /* Filter window color flavor text */ -.bm-filter-color small { +#bm-window-filter .bm-filter-color small { font-size: 0.75em; } @@ -99,20 +99,20 @@ /* WINDOWED MODE */ /* Filter flex in windowed mode */ -.bm-windowed #bm-filter-flex { +#bm-window-filter.bm-windowed #bm-filter-flex { flex-direction: column; gap: 0.25em; } /* Filter color in windowed mode */ -.bm-windowed .bm-filter-color { +#bm-window-filter.bm-windowed .bm-filter-color { width: auto; margin: 0; padding: 0; } /* Filter window container for RGB color display in windowed mode */ -.bm-windowed .bm-filter-container-rgb { +#bm-window-filter.bm-windowed .bm-filter-container-rgb { display: flex; width: 100%; gap: 0.5ch; @@ -128,11 +128,16 @@ } /* Filter window hide color button SVG in windowed mode */ -.bm-windowed .bm-filter-container-rgb svg { +#bm-window-filter.bm-windowed .bm-filter-container-rgb svg { width: 3ch; } /* Filter window header 2 in windowed mode */ -.bm-windowed .bm-filter-color h2 { +#bm-window-filter.bm-windowed .bm-filter-color h2 { font-size: 0.75em; +} + +/* Filter window dragbar text area in windowed mode */ +#bm-window-filter #bm-filter-windowed-color-totals { + font-size: 1em; } \ No newline at end of file diff --git a/src/WindowFilter.js b/src/WindowFilter.js index 68e0110..9cedaa8 100644 --- a/src/WindowFilter.js +++ b/src/WindowFilter.js @@ -124,7 +124,7 @@ export default class WindowFilter extends Overlay { .addSpan({'id': 'bm-filter-tot-completed', 'innerHTML': '??? ???'}).buildElement() .buildElement() .addDiv({'class': 'bm-container'}) - .addP({'innerHTML': `Colors with the icon ${this.eyeOpen.replace(' { - button.onclick = () => instance.handleMinimization(button); + button.onclick = () => { + const windowedColorTotals = document.querySelector('#bm-filter-windowed-color-totals'); + if (windowedColorTotals) { + windowedColorTotals.style.display = (button.dataset['buttonStatus'] == 'expanded') ? 'none' : ''; + } + instance.handleMinimization(button); + }; button.ontouchend = () => {button.click()}; // Needed only to negate weird interaction with dragbar }).buildElement() - .addDiv().buildElement() // Contains the minimized h1 element + .addDiv() + .addSpan({'id': 'bm-filter-windowed-color-totals', 'class': 'bm-dragbar-text', 'style': 'font-weight: 700;'}).buildElement() // Contains correct / total pixel values + // Minimized h1 element will appear here + .buildElement() .addDiv({'class': 'bm-flex-center'}) .addButton({'class': 'bm-button-circle', 'textContent': '🗖', 'aria-label': 'Switch to fullscreen mode for "Color Filter"'}, (instance, button) => { button.onclick = () => { @@ -423,7 +432,7 @@ export default class WindowFilter extends Overlay { .addSmall({'textContent': `#${color.id.toString().padStart(2, 0)}`}).buildElement() .addSmall({'class': 'bm-filter-color-pxl-cnt', 'textContent': `${colorCorrectLocalized} / ${colorTotalLocalized}`}).buildElement() .buildElement() - .addP({'class': 'bm-filter-color-pxl-desc', 'textContent': `${((typeof colorIncorrect == 'number') && !isNaN(colorIncorrect)) ? colorIncorrect : '???'} incorrect pixels. Completed: ${colorPercent}`}).buildElement() + .addP({'class': 'bm-filter-color-pxl-desc', 'textContent': `${((typeof colorIncorrect == 'number') && !isNaN(colorIncorrect)) ? colorIncorrect : '???'} incorrect pixel${(colorIncorrect == 1) ? '' : 's'}. Completed: ${colorPercent}`}).buildElement() .buildElement() .buildElement(); } @@ -513,18 +522,21 @@ export default class WindowFilter extends Overlay { } } + /** The information about a specific color on the palette. + * @typedef {Object} ColorData + * @property {number | string} colorTotal + * @property {string} colorTotalLocalized + * @property {number | string} colorCorrect + * @property {string} colorCorrectLocalized + * @property {string} colorPercent + * @property {number} colorIncorrect + */ + /** Updates the information inside the colors in the color list. * If the color list does not exist yet, it returns the color information instead. * This assumes the information inside each element is the same between fullscreen and windowed mode. * @since 0.90.60 - * @returns {Object} */ updateColorList() { @@ -577,6 +589,22 @@ export default class WindowFilter extends Overlay { } } + // Obtains the correct / total pixels display element, or `undefined` if in fullscreen mode + const windowedColorTotals = document.querySelector('#bm-filter-windowed-color-totals'); + + // If the element exists... + if (windowedColorTotals) { + + // Returns the number, unlocalized (no space to localize) + // OR returns the three characters on either end of the string, with the middle replaced with an ellipse. + // E.g. '1234567' or '123…678' + const allCorrect = (this.allPixelsCorrectTotal.toString().length > 7) ? this.allPixelsCorrectTotal.toString().slice(0, 2) + '…' + this.allPixelsCorrectTotal.toString().slice(-3) : this.allPixelsCorrectTotal.toString(); + const allTotal = (this.allPixelsTotal.toString().length > 7) ? this.allPixelsTotal.toString().slice(0, 2) + '…' + this.allPixelsTotal.toString().slice(-3) : this.allPixelsTotal.toString(); + + // Updates the display with XSS protection enabled (because why not) + this.updateInnerHTML('#bm-filter-windowed-color-totals', `${allCorrect}/${allTotal}`, true); + } + // Return early if the color list does not exist. // We can't update DOM elements that don't exist, so we exit now. if (!colorList) {return colorStatistics;} @@ -610,7 +638,7 @@ export default class WindowFilter extends Overlay { // Updates the pixel description if it exists const pixelDesc = document.querySelector(`#${this.windowID} .bm-filter-color[data-id="${colorID}"] .bm-filter-color-pxl-desc`); - if (pixelDesc) {pixelDesc.textContent = `${((typeof colorIncorrect == 'number') && !isNaN(colorIncorrect)) ? colorIncorrect : '???'} incorrect pixels. Completed: ${colorPercent}`;} + if (pixelDesc) {pixelDesc.textContent = `${((typeof colorIncorrect == 'number') && !isNaN(colorIncorrect)) ? colorIncorrect : '???'} incorrect pixel${(colorIncorrect == 1) ? '' : 's'}. Completed: ${colorPercent}`;} } // Since the dataset has changed, we need to sort again diff --git a/src/WindowMain.js b/src/WindowMain.js index 425add7..3474085 100644 --- a/src/WindowMain.js +++ b/src/WindowMain.js @@ -163,6 +163,11 @@ export default class WindowMain extends Overlay { .addDiv({'class': 'bm-container bm-flex-between', 'style': 'margin-bottom: 0; flex-direction: column;'}) .addDiv({'class': 'bm-flex-between'}) // .addButton({'class': 'bm-button-circle', 'innerHTML': '🖌'}).buildElement() + .addButton({'class': 'bm-button-circle', 'innerHTML': '⚙️', 'title': 'Settings'}, (instance, button) => { + button.onclick = () => { + instance.settingsManager.buildWindow(); + } + }).buildElement() .addButton({'class': 'bm-button-circle', 'innerHTML': '🧙', 'title': 'Template Wizard'}, (instance, button) => { button.onclick = () => { const templateManager = instance.apiManager?.templateManager; diff --git a/src/WindowSettings.css b/src/WindowSettings.css new file mode 100644 index 0000000..53765e4 --- /dev/null +++ b/src/WindowSettings.css @@ -0,0 +1,79 @@ +/* @since 0.91.22 */ + +/* Highlight preset group container */ +#bm-window-settings div:has(> .bm-highlight-preset-container) { + width: fit-content; + justify-content: flex-start; +} + +/* Highlight preset container */ +#bm-window-settings .bm-highlight-preset-container { + display: flex; + flex-direction: column; + width: 13%; +} + +/* Highlight preset title */ +#bm-window-settings .bm-highlight-preset-container span { + width: fit-content; + margin: auto; + font-size: 0.7em; +} + +/* Highlight preset button */ +#bm-window-settings .bm-highlight-preset-container button { + width: fit-content; + padding: 0; + border-radius: 0; +} + +/* Highlight preset SVG */ +#bm-window-settings .bm-highlight-preset-container svg { + stroke: #333; + stroke-width: 0.02px; + width: 100%; + min-width: 1.5ch; + max-width: 14.5ch; +} + +/* Highlight preset SVG on hover/focus */ +#bm-window-settings .bm-highlight-preset-container button:hover svg, +#bm-window-settings .bm-highlight-preset-container button:focus svg { + opacity: 0.9; +} + +/* Highlight pattern container */ +#bm-window-settings .bm-highlight-grid { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + width: 25%; + min-width: 3ch; + max-width: 15ch; +} + +/* Highlight pattern button */ +#bm-window-settings .bm-highlight-grid > button { + width: 100%; + padding: 0; + aspect-ratio: 1 / 1; + background-color: white; + border: #333 1px solid; + border-radius: 0; + box-sizing: border-box; +} + +/* Highlight pattern button in 'Incorrect' mode */ +#bm-window-settings .bm-highlight-grid > button[data-status="Incorrect"] { + background-color: brown; +} + +/* Highlight pattern button in 'Template' mode */ +#bm-window-settings .bm-highlight-grid > button[data-status="Template"] { + background-color: darkslategray; +} + +/* Highlight pattern button when hovered/focused */ +#bm-window-settings .bm-highlight-grid > button:hover, +#bm-window-settings .bm-highlight-grid > button:focus { + opacity: 0.8; +} \ No newline at end of file diff --git a/src/WindowSettings.js b/src/WindowSettings.js new file mode 100644 index 0000000..a478e7b --- /dev/null +++ b/src/WindowSettings.js @@ -0,0 +1,97 @@ +import Overlay from "./Overlay"; + +/** The overlay builder for the settings window in Blue Marble. + * The logic for this window is managed in {@link SettingsManager} + * @description This class handles the overlay UI for the settings window of the Blue Marble userscript. + * @class WindowSettings + * @since 0.91.11 + * @see {@link Overlay} for examples + */ +export default class WindowSettings extends Overlay { + + /** Constructor for the Settings window + * @param {string} name - The name of the userscript + * @param {string} version - The version of the userscript + * @since 0.91.11 + * @see {@link Overlay#constructor} for examples + */ + constructor(name, version) { + super(name, version); // Executes the code in the Overlay constructor + this.window = null; // Contains the *window* DOM tree + this.windowID = 'bm-window-settings'; // The ID attribute for this window + this.windowParent = document.body; // The parent of the window DOM tree + } + + /** Spawns a Settings window. + * If another settings window already exists, we DON'T spawn another! + * Parent/child relationships in the DOM structure below are indicated by indentation. + * @since 0.91.11 + */ + buildWindow() { + + // If a settings window already exists, close it + if (document.querySelector(`#${this.windowID}`)) { + document.querySelector(`#${this.windowID}`).remove(); + return; + } + + this.window = this.addDiv({'id': this.windowID, 'class': 'bm-window'}) + .addDragbar() + .addButton({'class': 'bm-button-circle', 'textContent': '▼', 'aria-label': 'Minimize window "Color Filter"', 'data-button-status': 'expanded'}, (instance, button) => { + button.onclick = () => instance.handleMinimization(button); + button.ontouchend = () => {button.click()}; // Needed only to negate weird interaction with dragbar + }).buildElement() + .addDiv().buildElement() // Contains the minimized h1 element + .addDiv({'class': 'bm-flex-center'}) + .addButton({'class': 'bm-button-circle', 'textContent': '✖', 'aria-label': 'Close window "Color Filter"'}, (instance, button) => { + button.onclick = () => {document.querySelector(`#${this.windowID}`)?.remove();}; + button.ontouchend = () => {button.click();}; // Needed only to negate weird interaction with dragbar + }).buildElement() + .buildElement() + .buildElement() + .addDiv({'class': 'bm-window-content'}) + .addDiv({'class': 'bm-container bm-center-vertically'}) + .addHeader(1, {'textContent': 'Settings'}).buildElement() + .buildElement() + .addHr().buildElement() + .addP({'textContent': 'Settings take 5 seconds to save.'}).buildElement() + .addDiv({'class': 'bm-container bm-scrollable'}, (instance, div) => { + // Each category in the settings window + this.buildHighlight(); + this.buildTemplate(); + }).buildElement() + .buildElement() + .buildElement().buildOverlay(this.windowParent); + + // Creates dragging capability on the drag bar for dragging the window + this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`); + } + + /** Displays an error when a settings category fails to load. + * @param {string} name - The name of the category + * @since 0.91.11 + */ + #errorOverrideFailure(name) { + this.window = this.addDiv({'class': 'bm-container'}) + .addHeader(2, {'textContent': name}).buildElement() + .addHr().buildElement() + .addP({'innerHTML': `An error occured loading the ${name} category. SettingsManager failed to override the ${name} function inside WindowSettings.`}).buildElement() + .buildElement(); + } + + /** Builds the highlight section of the window. + * This should be overriden by {@link SettingsManager} + * @since 0.91.11 + */ + buildHighlight() { + this.#errorOverrideFailure('Pixel Highlight'); + } + + /** Builds the template section of the window. + * This should be overriden by {@link SettingsManager} + * @since 0.91.68 + */ + buildTemplate() { + this.#errorOverrideFailure('Template'); + } +} \ No newline at end of file diff --git a/src/main.css b/src/main.css index bc2c40c..bfa8ce9 100644 --- a/src/main.css +++ b/src/main.css @@ -6,4 +6,5 @@ @import './confettiManager.css'; @import './overlay.css'; @import './WindowFilter.css'; +@import './WindowSettings.css'; @import './WindowWizard.css'; diff --git a/src/main.js b/src/main.js index 94f47af..322c1b7 100644 --- a/src/main.js +++ b/src/main.js @@ -8,6 +8,7 @@ import TemplateManager from './templateManager.js'; import { consoleLog, consoleWarn } from './utils.js'; import WindowMain from './WindowMain.js'; import WindowTelemetry from './WindowTelemetry.js'; +import SettingsManager from './settingsManager.js'; const name = GM_info.script.name.toString(); // Name of userscript const version = GM_info.script.version.toString(); // Version of userscript @@ -186,19 +187,25 @@ if (!!(robotoMonoInjectionPoint.indexOf('@font-face') + 1)) { document.head?.appendChild(stylesheetLink); } +const userSettings = JSON.parse(GM_getValue('bmUserSettings', '{}')); // Loads the user settings + // CONSTRUCTORS const observers = new Observers(); // Constructs a new Observers object const windowMain = new WindowMain(name, version); // Constructs a new Overlay object for the main overlay -const templateManager = new TemplateManager(name, version, windowMain); // Constructs a new TemplateManager object +const templateManager = new TemplateManager(name, version); // Constructs a new TemplateManager object const apiManager = new ApiManager(templateManager); // Constructs a new ApiManager object +const settingsManager = new SettingsManager(name, version, userSettings); // Constructs a new SettingsManager +windowMain.setSettingsManager(settingsManager); // Sets the settings manager windowMain.setApiManager(apiManager); // Sets the API manager +templateManager.setWindowMain(windowMain); +templateManager.setSettingsManager(settingsManager); // Sets the settings manager + const storageTemplates = JSON.parse(GM_getValue('bmTemplates', '{}')); console.log(storageTemplates); templateManager.importJSON(storageTemplates); // Loads the templates -const userSettings = JSON.parse(GM_getValue('bmUserSettings', '{}')); // Loads the user settings console.log(userSettings); console.log(Object.keys(userSettings).length); diff --git a/src/overlay.css b/src/overlay.css index 37a0711..69320ab 100644 --- a/src/overlay.css +++ b/src/overlay.css @@ -92,7 +92,9 @@ } /* Header 1 when inside dragbar */ -.bm-dragbar h1 { +/* Or, when the custom class is used */ +.bm-dragbar h1, +.bm-dragbar-text { font-size: 1.2em; user-select: none; overflow: hidden; @@ -345,8 +347,10 @@ input[type="file"] { /* Containers for "sections" of elements in windowed mode */ /* Does not apply to the main window */ -.bm-container:not(#bm-window-main .bm-container) { - margin: 0.25em 0; +.bm-windowed .bm-container:not(#bm-window-main .bm-container) { + margin-top: 0.25em; + margin-bottom: 0.25em; + /* Do not use 'margin' shorthand, as it will override left/right margin */ } /* Header 1 in windowed mode */ diff --git a/src/settingsManager.js b/src/settingsManager.js new file mode 100644 index 0000000..849e999 --- /dev/null +++ b/src/settingsManager.js @@ -0,0 +1,331 @@ +import { sleep } from "./utils"; +import WindowSettings from "./WindowSettings"; + +/** SettingsManager class for handling user settings and making them persist between sessions. + * Logic for {@link WindowSettings} is managed here. + * "Flags" should follow the same styling as `.classList()` and should not contain spaces. + * A flag should always be false by default. + * When a flag is false, it will not exist in the "flags" Array. + * (Therefore, "flags" should be `[]` by default) + * If it exists in the "flags" Array, then the flag is `true`. + * @class SettingsManager + * @since 0.91.11 + * @example + * { + * "uuid": "497dcba3-ecbf-4587-a2dd-5eb0665e6880", + * "telemetry": 1, + * "flags": ["hl-noTrans", "ftr-oWin", "te-noSkip"], + * "highlight": [[1,0,-1],[1,-1,0],[2,1,0],[1,0,1]], + * "filter": [-2,0,4,5,6,29,63] + * } + */ +export default class SettingsManager extends WindowSettings { + + /** Constructor for the SettingsManager class + * @param {string} name - The name of the userscript + * @param {string} version - The version of the userscript + * @param {Object} userSettings - The user settings as an object + * @since 0.91.11 + */ + constructor(name, version, userSettings) { + super(name, version); // Executes WindowSettings constructor + + this.userSettings = userSettings; // User settings as an Object + this.userSettings.flags ??= []; // Makes sure the key "flags" always exists + this.userSettingsOld = structuredClone(this.userSettings); // Creates a duplicate of the user settings to store the old version of user settings from 5+ seconds ago + this.userSettingsSaveLocation = 'bmUserSettings'; // Storage save location + + this.updateFrequency = 5000; // Cooldown between saving to storage (throttle) + this.lastUpdateTime = 0; // When this unix timestamp is within the last 5 seconds, we should save this.userSettings to storage + + setInterval(this.updateUserStorage.bind(this), this.updateFrequency); // Runs every X seconds (see updateFrequency) + } + + /** Updates the user settings in userscript storage + * @since 0.91.39 + */ + async updateUserStorage() { + + // Turns the objects into a string + const userSettingsCurrent = JSON.stringify(this.userSettings); + const userSettingsOld = JSON.stringify(this.userSettingsOld); + + // If the user settings have changed, AND the last update to user storage was over 5 seconds ago (5sec throttle)... + if ((userSettingsCurrent != userSettingsOld) && ((Date.now() - this.lastUpdateTime) > this.updateFrequency)) { + await GM.setValue(this.userSettingsSaveLocation, userSettingsCurrent); // Updates user storage + this.userSettingsOld = structuredClone(this.userSettings); // Updates the old user settings with a duplicate of the current user settings + this.lastUpdateTime = Date.now(); // Updates the variable that contains the last time updated + console.log(userSettingsCurrent); + } + } + + /** Toggles a boolean flag to the state that was passed in. + * If no state was passed in, the flag will flip to the opposite state. + * The existence of the flag determines its state. If it exists, it is `true`. + * @param {string} flagName - The name of the flag to toggle + * @param {boolean} [state=undefined] - (Optional) The state to change the flag to + * @since 0.91.60 + */ + toggleFlag(flagName, state = undefined) { + + const flagIndex = this.userSettings?.flags?.indexOf(flagName) ?? -1; // Is the flag `true`? + + // If the flag is enabled, AND the user does not want to force the flag to be true... + if ((flagIndex != -1) && (state !== true)) { + + this.userSettings?.flags?.splice(flagIndex, 1); // Remove the flag (makes it false) + } else if ((flagIndex == -1) && (state !== false)) { + // Else if the flag is disabled, AND the user does not want to force the flag to be false... + this.userSettings?.flags?.push(flagName); // Add the flag (makes it true) + } + } + + // This is one of the most insane OOP setups I have ever laid my eyes on + + /** Builds the "highlight" category of the settings window + * @since 0.91.18 + * @see WindowSettings#buildHighlight + */ + buildHighlight() { + + const highlightPresetOff = ''; + const highlightPresetCross = ''; + + // Obtains user settings for highlight from storage, or the default array if nothing was found + const storedHighlight = this.userSettings?.highlight ?? [[1, 0, 1], [2, 0, 0], [1, -1, 0], [1, 1, 0], [1, 0, -1]]; + + // Constructs the category and adds it to the window + this.window = this.addDiv({'class': 'bm-container'}) + .addHeader(2, {'textContent': 'Pixel Highlight'}).buildElement() + .addHr().buildElement() + .addDiv({'class': 'bm-container', 'style': 'margin-left: 1.5ch;'}) + .addCheckbox({'textContent': 'Highlight transparent pixels'}, (instance, label, checkbox) => { + checkbox.checked = !this.userSettings?.flags?.includes('hl-noTrans'); // Makes the checkbox match the last stored user setting + checkbox.onchange = (event) => this.toggleFlag('hl-noTrans', !event.target.checked); // Forces the flag to be the opposite state as the checkbox. E.g. "Checked" means 'hl-noTrans' is false (does not exist). + }).buildElement() + .addP({'id': 'bm-highlight-preset-label', 'textContent': 'Choose a preset:', 'style': 'font-weight: 700;'}).buildElement() + .addDiv({'class': 'bm-flex-center', 'role': 'group', 'aria-labelledby': 'bm-highlight-preset-label'}) + .addDiv({'class': 'bm-highlight-preset-container'}) + .addSpan({'textContent': 'None'}).buildElement() + .addButton({'innerHTML': highlightPresetOff, 'aria-label': 'Preset "None"'}, (instance, button) => {button.onclick = () => this.#updateHighlightToPreset('None')}).buildElement() + .buildElement() + .addDiv({'class': 'bm-highlight-preset-container'}) + .addSpan({'textContent': 'Cross'}).buildElement() + .addButton({'innerHTML': highlightPresetCross, 'aria-label': 'Preset "Cross Shape"'}, (instance, button) => {button.onclick = () => this.#updateHighlightToPreset('Cross')}).buildElement() + .buildElement() + .addDiv({'class': 'bm-highlight-preset-container'}) + .addSpan({'textContent': 'X'}).buildElement() + .addButton({'innerHTML': highlightPresetCross.replace('d="M1,0H2V1H3V2H2V3H1V2H0V1H1Z"', 'd="M0,0V1H3V0H2V3H3V2H0V3H1V0Z"'), 'aria-label': 'Preset "X Shape"'}, (instance, button) => {button.onclick = () => this.#updateHighlightToPreset('X')}).buildElement() + .buildElement() + .addDiv({'class': 'bm-highlight-preset-container'}) + .addSpan({'textContent': 'Full'}).buildElement() + .addButton({'innerHTML': highlightPresetOff.replace('#fff', '#2f4f4f'), 'aria-label': 'Preset "Full Template"'}, (instance, button) => {button.onclick = () => this.#updateHighlightToPreset('Full')}).buildElement() + .buildElement() + .buildElement() + .addP({'id': 'bm-highlight-grid-label', 'textContent': 'Create a custom pattern:', 'style': 'font-weight: 700;'}).buildElement() + .addDiv({'class': 'bm-highlight-grid', 'role': 'group', 'aria-labelledby': 'bm-highlight-grid-label'}); + // We leave this open so we can add buttons + + // For each of the 9 buttons... + for (let buttonY = -1; buttonY <= 1; buttonY++) { + for (let buttonX = -1; buttonX <= 1; buttonX++) { + const buttonState = storedHighlight[storedHighlight.findIndex(([, x, y]) => ((x == buttonX) && (y == buttonY)))]?.[0] ?? 0; + let buttonStateName = 'Disabled'; + if (buttonState == 1) { + buttonStateName = 'Incorrect'; + } else if (buttonState == 2) { + buttonStateName = 'Template'; + } + this.window = this.addButton({ + 'data-status': buttonStateName, + 'aria-label': `Sub-pixel ${buttonStateName.toLowerCase()}` + }, (instance, button) => { + button.onclick = () => this.#updateHighlightSettings(button, [buttonX, buttonY]) + }).buildElement(); + } + } + + // Resumes from where we left off before we added buttons + this.window = this.buildElement() + .buildElement() + .buildElement(); + } + + /** Updates the display of the highlight buttons in the settings window. + * Additionally, it will update user settings with the new selection. + * @param {HTMLButtonElement} button - The button that was pressed + * @param {Array} coords - The relative coordinates of the button + * @since 0.91.46 + */ + #updateHighlightSettings(button, coords) { + + button.disabled = true; // Disabled the button until we are done + + const status = button.dataset['status']; // Obtains the current status of the button + + /** Obtains the old highlight storage, or sets it to default. @type {Array} */ + const userStorageOld = this.userSettings?.highlight ?? [[1, 0, 1], [2, 0, 0], [1, -1, 0], [1, 1, 0], [1, 0, -1]]; + + let userStorageChange = [2, 0, 0]; // The new change to the user storage + + const userStorageNew = userStorageOld; // The old storage with the new change + + // For each different type of status... + switch (status) { + + // If the button was in the "Disabled" state + case 'Disabled': + + // Change to "Incorrect" + button.dataset['status'] = 'Incorrect'; + button.ariaLabel = 'Sub-pixel incorrect'; + userStorageChange = [1, ...coords]; + break; + + // If the button was in the "Incorrect" state + case 'Incorrect': + + // Change to "Template" + button.dataset['status'] = 'Template'; + button.ariaLabel = 'Sub-pixel template'; + userStorageChange = [2, ...coords]; + break; + + // If the button was in the "Template" state + case 'Template': + + // Change to "Disabled" + button.dataset['status'] = 'Disabled'; + button.ariaLabel = 'Sub-pixel disabled'; + userStorageChange = [0, ...coords]; + break; + } + + // Finds the index of the pixel to change + const indexOfChange = userStorageOld.findIndex(([, x, y]) => ((x == userStorageChange[1]) && (y == userStorageChange[2]))); + + // If the new sub-pixel state is NOT disabled + if (userStorageChange[0] != 0) { + + // If a sub-pixel was found... + if (indexOfChange != -1) { + userStorageNew[indexOfChange] = userStorageChange; + } else { + userStorageNew.push(userStorageChange); + } + } else if (indexOfChange != -1) { + // Else, it is disabled. We want to remove it if it exists. + userStorageNew.splice(indexOfChange, 1); // Removes 1 index from the array at the index of the pixel change + } + + this.userSettings['highlight'] = userStorageNew; + // TODO: Add timer update here + + button.disabled = false; // Reenables the button since we are done + } + + /** Changes the highlight buttons to the clicked preset. + * @param {string} preset - The name of the preset + * @since 0.91.49 + */ + async #updateHighlightToPreset(preset) { + + // Obtains all preset buttons as a NodeList + const presetButtons = document.querySelectorAll('.bm-highlight-preset-container button'); + + // For each preset... + for (const button of presetButtons) { + button.disabled = true; // Disables the button + } + + let presetArray = [0,0,0,0,2,0,0,0,0]; // The preset "None" + + // Selects the preset passed in + switch (preset) { + case 'Cross': + presetArray = [0,1,0,1,2,1,0,1,0]; // The preset "Cross" + break; + case 'X': + presetArray = [1,0,1,0,2,0,1,0,1]; // The preset "X" + break; + case 'Full': + presetArray = [2,2,2,2,2,2,2,2,2]; // The preset "Full" + break; + } + + // Obtains the buttons to click as a NodeList + const buttons = document.querySelector('.bm-highlight-grid')?.childNodes ?? []; + + // For each button... + for (let buttonIndex = 0; buttonIndex < buttons.length; buttonIndex++) { + + const button = buttons[buttonIndex]; // Gets the current button to check + + // Gets the state of the button as a number + let buttonState = button.dataset['status']; + buttonState = (buttonState != 'Disabled') ? ((buttonState != 'Incorrect') ? 2 : 1) : 0; + + // Finds the difference between the preset and the button + let buttonStateDelta = presetArray[buttonIndex] - buttonState; + + // Since there is no difference, the button matches, so we skip it + if (buttonStateDelta == 0) {continue;} + + // Makes the difference positive + buttonStateDelta += (buttonStateDelta < 0) ? 3 : 0; + + /** At this point, these are the possible options: + * 1. The preset is zero and the button is two (-2) so we need to click once + * 2. The preset is one and the button is two (-1) so we need to click twice + * 3. The preset is one ahead of the button (1) so we need to click once + * 4. The preset is two ahead of the button (2) so we need to click twice + * Due to the addition of three in the line above, options 1 & 3 combine, and options 2 & 4 combine. + * Now the only options we have are: + * 1. If (1) then click once + * 2. If (2) then click twice + * Also due to the addition of three in the line above, our two options are POSITIVE numbers + */ + + button.click(); // Clicks once + + // Clicks a second time if needed + if (buttonStateDelta == 2) { + + // For 0.2 seconds, or when the button is NOT disabled, wait for 10 milliseconds before attempting to continue + for (let timeWaited = 0; timeWaited < 200; timeWaited += 10) { + if (!button.disabled) {break;} // Breaks early once the button is enabled + await sleep(10); + } + + button.click(); // Clicks again + } + } + + // For each preset... + for (const button of presetButtons) { + button.disabled = false; // Re-enables the button + } + } + + /** Build the "template" category of settings window + * @since 0.91.68 + * @see WindowSettings#buildTemplate + */ + buildTemplate() { + + this.window = this.addDiv({'class': 'bm-container'}) + .addHeader(2, {'textContent': 'Pixel Highlight'}).buildElement() + .addHr().buildElement() + .addDiv({'class': 'bm-container', 'style': 'margin-left: 1.5ch;'}) + .addCheckbox({'textContent': 'Template creation should skip transparent tiles'}, (instance, label, checkbox) => { + checkbox.checked = !this.userSettings?.flags?.includes('hl-noSkip'); // Makes the checkbox match the last stored user setting + checkbox.onchange = (event) => this.toggleFlag('hl-noSkip', !event.target.checked); // If the user wants to skip, then the checkbox is NOT checked + }).buildElement() + .addCheckbox({'innerHTML': 'Experimental: Template creation should aggressively skip transparent tiles'}, (instance, label, checkbox) => { + checkbox.checked = this.userSettings?.flags?.includes('hl-agSkip'); // Makes the checkbox match the last stored user setting + checkbox.onchange = (event) => this.toggleFlag('hl-agSkip', event.target.checked); // If the user wants to aggressively skip, then the checkbox is checked + }).buildElement() + .buildElement() + .buildElement() + } +} \ No newline at end of file diff --git a/src/templateManager.js b/src/templateManager.js index 70e1660..1a7ce13 100644 --- a/src/templateManager.js +++ b/src/templateManager.js @@ -1,5 +1,7 @@ +import SettingsManager from "./settingsManager"; import Template from "./Template"; import { base64ToUint8, colorpaletteForBlueMarble, consoleError, consoleLog, consoleWarn, localizeNumber, numberToEncoded, sleep, viewCanvasInNewTab } from "./utils"; +import WindowMain from "./WindowMain"; import WindowWizard from "./WindowWizard"; /** Manages the template system. @@ -83,14 +85,17 @@ import WindowWizard from "./WindowWizard"; export default class TemplateManager { /** The constructor for the {@link TemplateManager} class. + * @param {string} name - The name of the userscript + * @param {string} version - The version of the userscript (SemVer as string) * @since 0.55.8 */ - constructor(name, version, overlay) { + constructor(name, version) { // Meta this.name = name; // Name of userscript this.version = version; // Version of userscript - this.overlay = overlay; // The main instance of the Overlay class + this.windowMain = null; // The main instance of the Overlay class + this.settingsManager = null; // The main instance of the SettingsManager class this.schemaVersion = '2.0.0'; // Version of JSON schema this.userID = null; // The ID of the current user this.encodingBase = '!#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~'; // Characters to use for encoding/decoding @@ -111,6 +116,22 @@ export default class TemplateManager { this.shouldFilterColor = new Map(); } + /** Updates the stored instance of the main window. + * @param {WindowMain} windowMain - The main window instance + * @since 0.91.54 + */ + setWindowMain(windowMain) { + this.windowMain = windowMain; + } + + /** Updates the stored instance of the SettingsManager. + * @param {SettingsManager} settingsManager - The settings manager instance + * @since 0.91.54 + */ + setSettingsManager(settingsManager) { + this.settingsManager = settingsManager; + } + /** Creates the JSON object to store templates in * @returns {{ whoami: string, scriptVersion: string, schemaVersion: string, templates: Object }} The JSON object * @since 0.65.4 @@ -135,7 +156,7 @@ export default class TemplateManager { // Creates the JSON object if it does not already exist if (!this.templatesJSON) {this.templatesJSON = await this.createJSON(); console.log(`Creating JSON...`);} - this.overlay.handleDisplayStatus(`Creating template at ${coords.join(', ')}...`); + this.windowMain.handleDisplayStatus(`Creating template at ${coords.join(', ')}...`); // Creates a new template instance const template = new Template({ @@ -145,8 +166,16 @@ export default class TemplateManager { file: blob, coords: coords }); + + // Does the user want to skip transparent tiles while creating templates? + const shouldSkipTransTiles = !this.settingsManager?.userSettings?.flags?.includes('hl-noSkip'); + + // Does the user want to aggressively skip transparent tiles while creating templates? + const shouldAggSkipTransTiles = this.settingsManager?.userSettings?.flags?.includes('hl-agSkip'); + + console.log(`Should Skip: ${shouldSkipTransTiles}; Should Agg Skip: ${shouldAggSkipTransTiles}`); - const { templateTiles, templateTilesBuffers } = await template.createTemplateTiles(this.tileSize, this.paletteBM); // Chunks the tiles + const { templateTiles, templateTilesBuffers } = await template.createTemplateTiles(this.tileSize, this.paletteBM, shouldSkipTransTiles, shouldAggSkipTransTiles); // Chunks the tiles template.chunked = templateTiles; // Stores the chunked tile bitmaps @@ -166,7 +195,7 @@ export default class TemplateManager { this.templatesArray = []; // Remove this to enable multiple templates (2/2) this.templatesArray.push(template); // Pushes the Template object instance to the Template Array - this.overlay.handleDisplayStatus(`Template created at ${coords.join(', ')}!`); + this.windowMain.handleDisplayStatus(`Template created at ${coords.join(', ')}!`); console.log(Object.keys(this.templatesJSON.templates).length); console.log(this.templatesJSON); @@ -497,12 +526,12 @@ export default class TemplateManager { const pixelCountFormatted = localizeNumber(totalPixels); // Display status information about the templates being rendered - this.overlay.handleDisplayStatus( + this.windowMain.handleDisplayStatus( `Displaying ${templateCount} template${templateCount == 1 ? '' : 's'}.\nTotal pixels: ${pixelCountFormatted}` ); } else { //this.overlay.handleDisplayStatus(`Displaying ${templateCount} templates.`); - this.overlay.handleDisplayStatus(`Sleeping\nVersion: ${this.version}`); + this.windowMain.handleDisplayStatus(`Sleeping\nVersion: ${this.version}`); return tileBlob; // No templates are on this tile. Return the original tile early } @@ -523,6 +552,26 @@ export default class TemplateManager { const tileBeforeTemplates = context.getImageData(0, 0, drawSize, drawSize); const tileBeforeTemplates32 = new Uint32Array(tileBeforeTemplates.data.buffer); + + // Obtains the highlight pattern + const highlightPattern = this.settingsManager?.userSettings?.highlight || [[2, 0, 0]]; + // The code demands that a highlight pattern always exists. + // Therefore, to disable highlighting, the highlight pattern is `[[2, 0, 0]]`. + // `[[2, 0, 0]]` is special, and will skip the highlighting code altogether. + // As a side-effect, the template will always display while enabled. + // You can't disable all sub-pixels in order to hide the template. + + // Contains the first index of the highlight pattern. + const highlightPatternIndexZero = highlightPattern?.[0]; + // This is so we can later determine if the pattern is the preset "None" + + // Should highlighting be disabled? + const highlightDisabled = ( + (highlightPattern?.length == 1) + && (highlightPatternIndexZero?.[0] == 2) + && (highlightPatternIndexZero?.[1] == 0) + && (highlightPatternIndexZero?.[2] == 0) + ) // For each template in this tile, draw them. for (const template of templatesToDraw) { @@ -557,7 +606,9 @@ export default class TemplateManager { } = this.#calculateCorrectPixelsOnTile_And_FilterTile({ tile: tileBeforeTemplates32, template: templateBeforeFilter32, - templateInfo: [coordXtoDrawAt, coordYtoDrawAt, template.bitmap.width, template.bitmap.height] + templateInfo: [coordXtoDrawAt, coordYtoDrawAt, template.bitmap.width, template.bitmap.height], + highlightPattern: highlightPattern, + highlightDisabled: highlightDisabled }); let pixelsCorrectTotal = 0; @@ -573,7 +624,8 @@ export default class TemplateManager { // If there are colors to filter, then we draw the filtered template on the canvas // Or, if there are Erased (#deface) pixels, then we draw the modified template on the canvas - if ((this.shouldFilterColor.size != 0) || templateHasErased) { + // Or, if the user has enabled highlighting, then we draw the modified template on the canvas + if ((this.shouldFilterColor.size != 0) || templateHasErased || !highlightDisabled) { console.log('Colors to filter: ', this.shouldFilterColor); //context.putImageData(new ImageData(new Uint8ClampedArray(templateAfterFilter.buffer), template.bitmap.width, template.bitmap.height), coordXtoDrawAt, coordYtoDrawAt); context.drawImage(await createImageBitmap(new ImageData(new Uint8ClampedArray(templateAfterFilter.buffer), template.bitmap.width, template.bitmap.height)), coordXtoDrawAt, coordYtoDrawAt); @@ -654,7 +706,7 @@ export default class TemplateManager { } else { // We don't know what the schema is. Unsupported? - this.overlay.handleDisplayError(`Template version ${schemaVersion} is unsupported.\nUse Blue Marble version ${scriptVersion} or load a new template.`); + this.windowMain.handleDisplayError(`Template version ${schemaVersion} is unsupported.\nUse Blue Marble version ${scriptVersion} or load a new template.`); } /** Loads schema of Blue Marble template storage @@ -757,17 +809,22 @@ export default class TemplateManager { /** Calculates the correct pixels on this tile. * In addition, this function filters colors based on user input. * In addition, this function modifies colors to properly display (#deface). + * In addition, this function modifies incorrect pixels to display highlighting. * This function has multiple purposes only to reduce iterations of scans over every pixel on the template. * @param {Object} params - Object containing all parameters * @param {Uint32Array} params.tile - The tile without templates as a Uint32Array * @param {Uint32Array} params.template - The template without filtering as a Uint32Array * @param {Array} params.templateInfo - Information about template location and size + * @param {Array} params.highlightPattern - The highlight pattern selected by the user + * @param {boolean} params.highlightDisabled - Should highlighting be disabled? * @returns {{correctPixels: Map, filteredTemplate: Uint32Array}} A Map containing the color IDs (keys) and how many correct pixels there are for that color (values) */ #calculateCorrectPixelsOnTile_And_FilterTile({ tile: tile32, template: template32, - templateInfo: templateInformation + templateInfo: templateInformation, + highlightPattern: highlightPattern, + highlightDisabled: highlightDisabled }) { // Size of a pixel in actuality @@ -788,6 +845,10 @@ export default class TemplateManager { //console.log(`TemplateX: ${templateCoordX}\nTemplateY: ${templateCoordY}\nStarting Row:${templateCoordY+tilePixelOffsetY}\nStarting Column:${templateCoordX+tilePixelOffsetX}`); + // Obtains if the user wants to highlight tile pixels that are transparent, but the template pixel is not + const shouldTransparentTilePixelsBeHighlighted = !this.settingsManager?.userSettings?.flags?.includes('hl-noTrans'); + // The actual logic of this boolean is "should all pixels be highlighted" + const { palette: _, LUT: lookupTable } = this.paletteBM; // Obtains the palette and LUT // Makes a copy of the color palette Blue Marble uses, turns it into a Map, and adds data to count the amount of each color @@ -796,6 +857,10 @@ export default class TemplateManager { // For each center pixel... for (let templateRow = 1; templateRow < templateHeight; templateRow += pixelSize) { for (let templateColumn = 1; templateColumn < templateWidth; templateColumn += pixelSize) { + // ROWS ARE VERTICAL. "ROWS" AS IN, LIKE ON A SPREADSHEET + // COLUMNS ARE HORIZONTAL. "COLUMNS" AS IN, LIKE ON A SPREADSHEET + // THE FIFTH ROW IS FIVE DOWN FROM THE ZEROTH ROW + // THE THIRD COLUMN IS TO THE RIGHT OF THE FIRST COLUMN // The pixel on the tile to target (1 pixel above the template) const tileRow = (templateCoordY + templateRow) + tilePixelOffsetY; // (Template offset + current row) - 1 @@ -812,6 +877,9 @@ export default class TemplateManager { // Finds the best matching color ID for the template pixel. If none is found, default to "-2" const bestTemplateColorID = lookupTable.get(templatePixel) ?? -2; + // Finds the best matching color ID for the tile pixel. If none is found, default to "-2" + const bestTileColorID = lookupTable.get(tilePixelAbove) ?? -2; + // ----- COLOR FILTER ----- // If this pixel on the template is a color the user wants to hide on the canvas... if (this.shouldFilterColor.get(bestTemplateColorID)) { @@ -857,6 +925,39 @@ export default class TemplateManager { } // ----- END OF ERASED ----- + // ----- HIGHLIGHTING ----- + + // If highlighting is enabled, AND the template pixel is NOT transparent AND the template pixel does NOT match the tile pixel + if (!highlightDisabled && (templatePixelAlpha > tolerance) && (bestTileColorID != bestTemplateColorID)) { + + // If the tile pixel is NOT transparent, OR the user wants to highlight transparent pixels + if (shouldTransparentTilePixelsBeHighlighted || (tilePixelAlpha > tolerance)) { + + // Obtains the template color of this pixel + const templatePixelColor = template32[(templateRow * templateWidth) + templateColumn]; + // This will retrieve the tile background instead if the color is filtered! + + // For each of the 9 subpixels inside the pixel... + for (const subpixelPattern of highlightPattern) { + + // Deconstructs the sub pixel + const [subpixelState, subpixelColumnDelta, subpixelRowDelta] = subpixelPattern; + // "Delta" because the coordinate of the sub-pixel is relative to the center of the pixel + + // Obtains the subpixel color to use + const subpixelColor = (subpixelState != 0) ? ((subpixelState != 1) ? templatePixelColor : 0xFF0000FF) : 0x00000000; + // 0 = Transparent (black) + // 1 = Red (#FF0000) + // 2 = Template (matches template or hides if filtered) + + // Sets the subpixel to match the color on the highlight pattern + template32[((templateRow + subpixelRowDelta) * templateWidth) + (templateColumn + subpixelColumnDelta)] = subpixelColor; + } + } + } + + // ----- END OF HIGHLIGHTING ----- + // If the template pixel is Erased, and the tile pixel is transparent... if ((bestTemplateColorID == -1) && (tilePixelAbove <= tolerance)) { @@ -874,11 +975,10 @@ export default class TemplateManager { } // If the code passes this point, both pixels are opaque & not Erased. - // Finds the best matching color ID for the tile pixel. If none is found, default to "-2" - const bestTileColorID = lookupTable.get(tilePixelAbove) ?? -2; - - // If the template pixel does not match the tile pixel, then the pixel is skipped. - if (bestTileColorID != bestTemplateColorID) {continue;} + // If the template pixel does not match the tile pixel, then the pixel is skipped after highlighting. + if (bestTileColorID != bestTemplateColorID) { + continue; + } // If the code passes this point, the template pixel matches the tile pixel. // Increments the count by 1 for the best matching color ID (which can be negative). diff --git a/src/utils.js b/src/utils.js index fa917f0..a610b3c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -15,7 +15,7 @@ export function getWplaceVersion() { /** Halts execution of this specific userscript, for the specified time. * This will not block the thread. - * @param {number} - Time to wait in milliseconds + * @param {number} time - Time to wait in milliseconds * @since 0.88.483 * @returns {Promise} Promise of a setTimeout() */