Greasy Fork

Greasy Fork is available in English.

teste Overlay da Missão: Edição Pro

Overlay da Missão para o site WPlace

// ==UserScript==

// @name         teste Overlay da Missão: Edição Pro

// @namespace    http://tampermonkey.net/

// @version      2.4

// @description  Overlay da Missão para o site WPlace

// @author       Víkish

// @match        https://wplace.live/*

// @icon         https://www.google.com/s2/favicons?sz=64&domain=partidomissao.com

// @license      MIT

// @grant        none

// ==/UserScript==

(async function () {

'use strict';

/* =========================

   CONFIG

   ========================= */

const CHUNK_WIDTH = 1000;

const CHUNK_HEIGHT = 1000;

const UI_DEBOUNCE_MS = 200;       // debounce para atualização da UI

const TOUCH_SWIPE_THRESHOLD = 12; // px para considerar swipe (global)

const TOUCH_TAP_MAX_MS = 400;     // máximo de tempo para considerar tap

/* =========================

   HELPERS ASYNC: carregar overlays iniciais

   ========================= */

async function fetchData() {

    const response = await fetch("https://gist.githubusercontent.com/yl99a/45ec3df57cc75c4b93c45251b87eb20b/raw/overlays.json?" + Date.now());

    return await response.json();

}

function loadImage(src) {

    return new Promise((resolve, reject) => {

        const img = new Image();

        img.crossOrigin = "anonymous";

        img.onload = () => resolve({ img, width: img.naturalWidth, height: img.naturalHeight });

        img.onerror = reject;

        img.src = src;

    });

}

function blobToImage(blob) {

    return new Promise((resolve, reject) => {

        const img = new Image();

        img.onload = () => resolve(img);

        img.onerror = reject;

        img.src = URL.createObjectURL(blob);

    });

}

/* =========================

   WEB WORKER: cria worker dinamicamente (processamento pesado)

   ========================= */

const workerCode = `

self.onmessage = async function(e) {

  const data = e.data;

  if (!data || data.type !== 'process') return;

  const id = data.id;

  try {

    const blob = data.blob;

    const overlayBuf = data.overlayBuffer;

    const overlayWidth = data.overlayWidth;

    const overlayHeight = data.overlayHeight;

    const selectedArr = data.selectedColors || [];

    const selectedSet = new Set(selectedArr);

    const imgBitmap = await createImageBitmap(blob);

    const width = imgBitmap.width;

    const height = imgBitmap.height;

    const off = new OffscreenCanvas(width, height);

    const ctx = off.getContext('2d');

    ctx.drawImage(imgBitmap, 0, 0, width, height);

    const originalData = ctx.getImageData(0, 0, width, height);

    const resultData = ctx.getImageData(0, 0, width, height);

    const d1 = originalData.data;

    const dr = resultData.data;

    const d2 = new Uint8ClampedArray(overlayBuf);

    let wrongPixels = 0;

    let totalTargetPixels = 0;

    const localColorCount = Object.create(null);

    for (let i = 0; i < d1.length; i += 4) {

      const a2 = d2[i + 3];

      const isTransparent = a2 === 0;

      if (!isTransparent) totalTargetPixels++;

      const samePixel = d1[i] === d2[i] &&

                        d1[i+1] === d2[i+1] &&

                        d1[i+2] === d2[i+2] &&

                        d1[i+3] === d2[i+3];

      const key = d2[i] + ',' + d2[i+1] + ',' + d2[i+2];

      if (!samePixel && !isTransparent) {

        wrongPixels++;

        localColorCount[key] = (localColorCount[key] || 0) + 1;

      }

      if (samePixel && !isTransparent) {

        dr[i] = 0; dr[i+1] = 255; dr[i+2] = 0; dr[i+3] = 255;

      } else if (!isTransparent) {

        if (selectedSet.has(key)) {

          dr[i] = d2[i]; dr[i+1] = d2[i+1]; dr[i+2] = d2[i+2]; dr[i+3] = d2[i+3];

        } else {

          dr[i] = 0; dr[i+1] = 255; dr[i+2] = 0; dr[i+3] = 255;

        }

      }

    }

    ctx.putImageData(resultData, 0, 0);

    const outBlob = await off.convertToBlob();

    self.postMessage({ id, blob: outBlob, wrongPixels, totalTargetPixels, colorCounts: localColorCount });

  } catch (err) {

    self.postMessage({ id, error: String(err) });

  }

};

`;

const workerBlob = new Blob([workerCode], { type: "application/javascript" });

const workerUrl = URL.createObjectURL(workerBlob);

const pixelWorker = new Worker(workerUrl);

let workerReqId = 0;

const pendingWorker = new Map();

pixelWorker.onmessage = (ev) => {

    const data = ev.data;

    if (!data || typeof data.id === 'undefined') return;

    const id = data.id;

    const entry = pendingWorker.get(id);

    if (!entry) return;

    pendingWorker.delete(id);

    if (data.error) return entry.reject(new Error(data.error));

    entry.resolve(data);

};

/* =========================

   Carregar overlays e extrair imageData uma vez

   ========================= */

const overlays = await (async () => {

    const arr = await fetchData();

    for (const obj of arr) {

        obj.chunksString = `/${obj.chunk[0]}/${obj.chunk[1]}.png`;

        const { img, width, height } = await loadImage(obj.url);

        const overlayCanvas = new OffscreenCanvas(1000, 1000);

        const overlayCtx = overlayCanvas.getContext("2d");

        overlayCtx.drawImage(img, obj.coords[0], obj.coords[1], width, height);

        const imageData = overlayCtx.getImageData(0, 0, 1000, 1000);

        obj.imageData = imageData;

    }

    return arr;

})();

/* =========================

   Variáveis UI e estado

   ========================= */

const OVERLAY_MODES = ["overlay", "original", "chunks"];

let overlayMode = OVERLAY_MODES[0];

const counterContainer = document.createElement("div");

counterContainer.id = "pixel-counter";

Object.assign(counterContainer.style, {

    position: "fixed", top: "5px", left: "50%", transform: "translateX(-50%)",

    zIndex: "10000", padding: "6px 10px", fontFamily: "Arial, sans-serif",

    backgroundColor: "rgba(0,0,0,0.66)", color: "white", borderRadius: "6px",

    pointerEvents: "none", backdropFilter: "blur(3px)", lineHeight: "1.25",

    textAlign: "center", display: "flex", flexDirection: "column", gap: "2px"

});

document.body.appendChild(counterContainer);

const pixelCounter = document.createElement("div");

pixelCounter.textContent = "Pixeis restantes: 0";

pixelCounter.style.fontSize = "11px";

counterContainer.appendChild(pixelCounter);

const percentageCounter = document.createElement("div");

percentageCounter.textContent = "Progresso atual: 0,00%";

percentageCounter.style.fontSize = "11px";

counterContainer.appendChild(percentageCounter);

/* =========================

   UI: painel de cores (compacto)

   ========================= */

const colorStatsContainer = document.createElement("div");

Object.assign(colorStatsContainer.style, {

    position: "fixed", top: "170px", left: "10px",

    backgroundColor: "rgba(8,8,10,0.55)", color: "white", fontSize: "11px",

    padding: "0px", borderRadius: "8px", zIndex: "10000",

    maxHeight: "600px", pointerEvents: "auto",

    minWidth: "110px", maxWidth: "320px", boxSizing: "border-box",

    backdropFilter: "blur(6px)", touchAction: "manipulation"

});

document.body.appendChild(colorStatsContainer);

const styleTag = document.createElement('style');

styleTag.textContent = `

.color-list-scroll { overflow-y: auto; padding-right: 28px; position: relative; -ms-overflow-style: none; scrollbar-width: none; }

.color-list-scroll::-webkit-scrollbar { width: 0; height: 0; }

.color-checkbox { transform: scale(0.85); margin-left: 8px; margin-right: 2px; }

.color-item { display:flex; align-items:center; gap:8px; padding:6px 8px; border-radius:6px; }

.color-item:hover { background: rgba(255,255,255,0.03); }

.color-square { width: 18px; height: 18px; border-radius:3px; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.12); flex: 0 0 auto; }

.color-count { white-space: nowrap; pointer-events: auto; cursor: pointer; font-size: 11px; color: #fff; }

.color-header { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:8px 10px; }

.color-title { font-weight:600; font-size:13px; padding-right:6px; }

.color-divider { height:1px; background: rgba(255,255,255,0.06); margin:0 8px 6px 8px; }

.minimize-btn { width:28px; height:28px; display:inline-flex; align-items:center; justify-content:center; border-radius:4px; border: 1px solid rgba(255,255,255,0.8); background: rgba(255,255,255,0.02); cursor:pointer; flex:0 0 auto; padding:3px; box-sizing: border-box; color: white; font-weight:600; }

.color-header.minimized-centered { justify-content: center; padding: 6px 0; }

.color-stats-collapsed {

  width: 30px !important; height: 30px !important; min-width: 30px !important; max-width: 30px !important;

  padding: 0 !important; border-radius: 6px !important; overflow: hidden; display:flex; align-items:center; justify-content:center;

}

.color-stats-collapsed .color-header { padding: 0; justify-content: center; width:100%; height:100%; }

.color-stats-collapsed .color-title, .color-stats-collapsed .color-divider, .color-stats-collapsed .controls-box, .color-stats-collapsed .color-list-scroll { display:none !important; }

.btn-square-max { width:20px; height:20px; border-radius:4px; border:1.5px solid white !important; background:transparent !important; display:inline-flex; align-items:center; justify-content:center; padding:0; touch-action: none; }

.btn-square-min { width:28px; height:28px; border-radius:4px; border:1.5px solid white !important; background:transparent !important; display:inline-flex; align-items:center; justify-content:center; padding:0; font-size:16px; line-height:1; }

.custom-scroll-track { position:absolute; right:6px; width:16px; display:block; box-sizing:border-box; z-index:10001; pointer-events:auto; }

.custom-scroll-btn { position:absolute; left:0; right:0; height:18px; display:flex; align-items:center; justify-content:center; font-size:10px; user-select:none; cursor:pointer; background: rgba(255,255,255,0.02); border-radius:4px; }

.custom-scroll-btn.up { top:0; } .custom-scroll-btn.down { bottom:0; }

.custom-scroll-thumb { position:absolute; left:0; right:0; border-radius:8px; background: rgba(255,255,255,0.12); cursor:grab; min-height:20px; box-sizing:border-box; transition: background 120ms ease; }

.custom-scroll-thumb:active { cursor:grabbing; background: rgba(255,255,255,0.18); }

`;

document.head.appendChild(styleTag);

// HEADER e botões

const header = document.createElement('div');

header.className = 'color-header';

header.style.width = '100%';

header.style.boxSizing = 'border-box';

const title = document.createElement('div'); title.className = 'color-title'; title.textContent = 'Tabela de Cores'; header.appendChild(title);

const minimizeBtn = document.createElement('button');

minimizeBtn.className = 'minimize-btn btn-square-min';

minimizeBtn.title = 'Minimizar';

minimizeBtn.style.pointerEvents = 'auto';

minimizeBtn.innerHTML = '-';

header.appendChild(minimizeBtn);

colorStatsContainer.appendChild(header);

const divider = document.createElement('div'); divider.className = 'color-divider'; colorStatsContainer.appendChild(divider);

const controlsBox = document.createElement('div'); controlsBox.className = 'controls-box';

Object.assign(controlsBox.style, { padding: '6px 8px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px' });

controlsBox.style.pointerEvents = 'auto'; colorStatsContainer.appendChild(controlsBox);

const showAllWrapper = document.createElement('label'); showAllWrapper.className = 'controls-label'; showAllWrapper.style.cursor = 'pointer';

const showAllCheckbox = document.createElement('input'); showAllCheckbox.type = 'checkbox'; showAllCheckbox.checked = true; showAllCheckbox.style.marginRight = '6px'; showAllCheckbox.className = 'color-checkbox';

const showAllText = document.createElement('span'); showAllText.textContent = 'Mostrar todos';

showAllWrapper.appendChild(showAllCheckbox); showAllWrapper.appendChild(showAllText); controlsBox.appendChild(showAllWrapper);

const colorListWrapper = document.createElement('div'); colorListWrapper.className = 'color-list-scroll';

colorListWrapper.style.padding = '6px 8px 10px 8px'; colorListWrapper.style.position = 'relative'; colorListWrapper.style.overflowY = 'auto'; colorListWrapper.style.pointerEvents = 'auto';

colorStatsContainer.appendChild(colorListWrapper);

const colorList = document.createElement('div'); colorList.style.display = 'block'; colorListWrapper.appendChild(colorList);

/* custom scrollbar */

const customTrack = document.createElement('div'); customTrack.className = 'custom-scroll-track'; customTrack.style.position = 'absolute'; customTrack.style.pointerEvents = 'auto';

const customUpBtn = document.createElement('div'); customUpBtn.className = 'custom-scroll-btn up'; customUpBtn.textContent = 'â–²';

const customDownBtn = document.createElement('div'); customDownBtn.className = 'custom-scroll-btn down'; customDownBtn.textContent = 'â–¼';

const customThumb = document.createElement('div'); customThumb.className = 'custom-scroll-thumb';

customTrack.appendChild(customUpBtn); customTrack.appendChild(customThumb); customTrack.appendChild(customDownBtn);

colorStatsContainer.appendChild(customTrack);

/* popup nome da cor */

const namePopup = document.createElement("div");

Object.assign(namePopup.style, { position: "fixed", left: "0px", top: "0px", transform: "translate(-50%,-100%)", backgroundColor: "rgba(0,0,0,0.9)", color: "white", padding: "6px 8px", borderRadius: "6px", fontSize: "12px", whiteSpace: "nowrap", display: "none", zIndex: "10001", pointerEvents: "none" });

document.body.appendChild(namePopup);

/* =========================

   Estado e controle UI

   ========================= */

const selectedColors = new Set();

let lastColorCounts = {};

let showAll = true;

const savedCollapsed = localStorage.getItem('overlay_color_list_collapsed');

let listVisible = savedCollapsed === null ? false : (savedCollapsed === '0');

let initialSelectionDone = false;

/* debounce / merge updates */

let pendingColorCounts = null;

let uiDebounceTimer = null;

function scheduleColorUIUpdate(newCounts, wrongPixels, totalTarget) {

    pendingColorCounts = newCounts || {};

    if (typeof wrongPixels === 'number') {

        pixelCounter.textContent = `Pixeis restantes: ${wrongPixels}`;

        const percentage = totalTarget === 0 ? "100,00" : (((totalTarget - wrongPixels) / totalTarget) * 100).toFixed(2).replace(".", ",");

        percentageCounter.textContent = `Progresso atual: ${percentage}%`;

    }

    if (uiDebounceTimer) clearTimeout(uiDebounceTimer);

    uiDebounceTimer = setTimeout(() => {

        lastColorCounts = Object.fromEntries(Object.entries(pendingColorCounts || {}).filter(([,c])=>c>0));

        if (showAll) {

            for (const k of Object.keys(lastColorCounts)) selectedColors.add(k);

        }

        renderColorListImmediate();

        uiDebounceTimer = null;

    }, UI_DEBOUNCE_MS);

}

/* =========================

   Detecção global touch start/move/end para diferenciar swipe/tap

   ========================= */

let globalTouch = { startX:0, startY:0, startTime:0, isSwipe:false };

document.addEventListener('touchstart', (e) => {

    if (!e.touches || !e.touches[0]) return;

    const t = e.touches[0];

    globalTouch.startX = t.clientX; globalTouch.startY = t.clientY; globalTouch.startTime = Date.now(); globalTouch.isSwipe = false;

}, { passive: true });

document.addEventListener('touchmove', (e) => {

    if (!e.touches || !e.touches[0]) return;

    const t = e.touches[0];

    if (Math.abs(t.clientX - globalTouch.startX) > TOUCH_SWIPE_THRESHOLD || Math.abs(t.clientY - globalTouch.startY) > TOUCH_SWIPE_THRESHOLD) {

        globalTouch.isSwipe = true;

    }

}, { passive: true });

document.addEventListener('touchend', (e) => {

    setTimeout(()=>{ globalTouch.isSwipe = false; }, 50);

}, { passive: true });

/* =========================

   Funções de render / interação

   ========================= */

function rgbToHex(r,g,b){ const toHex=(v)=>v.toString(16).padStart(2,'0'); return `#${toHex(r)}${toHex(g)}${toHex(b)}`; }

function attachNameHandlers(el, hex) {

    el.addEventListener('pointerenter', (ev) => {

        if (ev.pointerType === 'touch') return;

        clearPopupTimers();

        showColorName(hex, el.getBoundingClientRect());

    });

    el.addEventListener('pointerleave', (ev) => {

        if (ev.pointerType === 'touch') return;

        clearPopupTimers();

        popupHideTimeout = setTimeout(()=>{ hideColorName(); }, 120);

    });

    el.addEventListener('click', (ev) => {

        if (ev.pointerType === 'touch') return;

        clearPopupTimers();

        showColorName(hex, el.getBoundingClientRect(), 1200);

    });

    let localTouchStartTime = 0;

    el.addEventListener('touchstart', (ev) => {

        if (!ev.touches || !ev.touches[0]) return;

        localTouchStartTime = Date.now();

    }, { passive: true });

    el.addEventListener('touchend', (ev) => {

        const dur = Date.now() - localTouchStartTime;

        if (globalTouch.isSwipe) return;

        if (dur > TOUCH_TAP_MAX_MS) return;

        clearPopupTimers();

        showColorName(hex, el.getBoundingClientRect(), 1200);

    }, { passive: true });

}

let popupShowTimeout = null, popupHideTimeout = null, popupPersistentTimeout = null;

function clearPopupTimers(){

    if (popupShowTimeout){ clearTimeout(popupShowTimeout); popupShowTimeout = null; }

    if (popupHideTimeout){ clearTimeout(popupHideTimeout); popupHideTimeout = null; }

    if (popupPersistentTimeout){ clearTimeout(popupPersistentTimeout); popupPersistentTimeout = null; }

}

function showColorName(hex, rect, persistForMs = 0) {

    clearPopupTimers();

    namePopup.textContent = colorNameMap[hex] || hex;

    const left = rect.left + rect.width/2;

    let top = rect.top - 6;

    if (top < 8) top = rect.top + rect.height + 6;

    namePopup.style.left = `${left}px`; namePopup.style.top = `${top}px`; namePopup.style.display = 'block';

    if (persistForMs > 0) popupPersistentTimeout = setTimeout(()=>{ hideColorName(); }, persistForMs);

}

function hideColorName(){ clearPopupTimers(); namePopup.style.display = 'none'; }

/* =========================

   renderColorListImmediate

   ========================= */

function renderColorListImmediate() {

    colorList.innerHTML = "";

    const entries = Object.entries(lastColorCounts || {}).filter(([,cnt]) => cnt > 0).sort((a,b)=>b[1]-a[1]);

    if (!initialSelectionDone && showAll) {

        for (const [key] of entries) selectedColors.add(key);

        initialSelectionDone = true;

    }

    for (const [key, count] of entries) {

        const [r,g,b] = key.split(",").map(Number);

        const hex = rgbToHex(r,g,b);

        const item = document.createElement('div');

        item.className = 'color-item'; item.style.pointerEvents = 'auto';

        const colorSquare = document.createElement('div'); colorSquare.className = 'color-square';

        colorSquare.style.backgroundColor = `rgb(${r}, ${g}, ${b})`; colorSquare.title = colorNameMap[hex] || '';

        const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.className = 'color-checkbox';

        checkbox.checked = selectedColors.has(key);

        checkbox.addEventListener('change', (e)=> {

            if (e.target.checked) selectedColors.add(key); else selectedColors.delete(key);

            const allKeys = Object.keys(lastColorCounts).filter(k=>lastColorCounts[k]>0);

            showAll = allKeys.length > 0 && allKeys.every(k=>selectedColors.has(k));

            showAllCheckbox.checked = !!showAll;

            refreshChunks();

            scheduleColorUIUpdate(lastColorCounts);

        });

        const label = document.createElement('span'); label.className = 'color-count'; label.textContent = `${count}`;

        attachNameHandlers(colorSquare, hex); attachNameHandlers(label, hex);

        item.appendChild(colorSquare); item.appendChild(checkbox); item.appendChild(label);

        colorList.appendChild(item);

    }

    if (Object.keys(lastColorCounts).length === 0) {

        const aviso = document.createElement("div"); aviso.textContent = "Sem cores detectadas ainda."; aviso.style.fontSize = "10px"; aviso.style.opacity = "0.85"; aviso.style.marginTop = "4px";

        colorList.appendChild(aviso);

    }

    needsLayoutUpdate = true;

    scheduleUIUpdate();

}

/* ================ fim PARTE 1/2 ================ */

/* =========================

   CONTINUAÇÃO: bindings, fetch proxy que usa o worker, UI helpers e finalização

   ========================= */

/* =========================

   Debounce/Layout scheduler

   ========================= */

let rafScheduled = false;

let needsRenderColors = false;

let needsLayoutUpdate = false;

function scheduleUIUpdate() {

    if (rafScheduled) return;

    rafScheduled = true;

    requestAnimationFrame(() => {

        rafScheduled = false;

        if (needsLayoutUpdate) {

            needsLayoutUpdate = false;

            try { updateListMaxHeight(); updateCustomTrackSize(); updateCustomThumb(); } catch (e) {}

        }

        if (needsRenderColors) {

            needsRenderColors = false;

            try { renderColorListImmediate(); } catch (e) {}

        }

    });

}

/* =========================

   resize/scroll helpers para o painel

   ========================= */

function parsePx(v){ return Math.round(parseFloat(v) || 0); }

function updateListMaxHeight(){

    try {

        let containerH = colorStatsContainer.clientHeight || Math.round(colorStatsContainer.getBoundingClientRect().height || 0);

        const headerH = header.offsetHeight || Math.round(header.getBoundingClientRect().height || 0);

        const dividerH = divider.offsetHeight || Math.round(divider.getBoundingClientRect().height || 0);

        const controlsH = controlsBox.offsetHeight || Math.round(controlsBox.getBoundingClientRect().height || 0);

        const extra = 8;

        if (!containerH || containerH < 80) {

            const vp = Math.round(window.innerHeight || document.documentElement.clientHeight || 600);

            containerH = Math.max(280, Math.round(vp * 0.30));

        }

        let maxH = Math.max(0, containerH - headerH - dividerH - controlsH - extra);

        const MIN_LIST_HEIGHT = 280;

        if (maxH < MIN_LIST_HEIGHT) maxH = Math.max(MIN_LIST_HEIGHT, Math.round(window.innerHeight * 0.25));

        colorListWrapper.style.maxHeight = `${maxH}px`;

        colorListWrapper.style.display = listVisible ? 'block' : colorListWrapper.style.display || 'block';

    } catch (e) {

        colorListWrapper.style.maxHeight = '420px';

        colorListWrapper.style.display = 'block';

    }

}

function updateCustomTrackSize(){

    try {

        const cs = getComputedStyle(colorListWrapper);

        const padTop = parsePx(cs.paddingTop);

        const padBottom = parsePx(cs.paddingBottom);

        const wrapperH = colorListWrapper.clientHeight || Math.round(colorListWrapper.getBoundingClientRect().height || 0);

        const trackInnerH = Math.max(0, wrapperH - padTop - padBottom);

        const wrapperRect = colorListWrapper.getBoundingClientRect();

        const containerRect = colorStatsContainer.getBoundingClientRect();

        const topRel = Math.round(wrapperRect.top - containerRect.top + padTop);

        customTrack.style.top = `${topRel}px`;

        customTrack.style.height = `${trackInnerH}px`;

        const sh = colorListWrapper.scrollHeight;

        const ch = colorListWrapper.clientHeight;

        customTrack.style.display = (sh <= ch) ? 'none' : 'block';

    } catch (e) {

        customTrack.style.top = `6px`;

        customTrack.style.height = `${Math.max(0, colorListWrapper.clientHeight - 12)}px`;

    }

}

function updateCustomThumb(){

    updateCustomTrackSize();

    const trackRect = customTrack.getBoundingClientRect();

    const ch = colorListWrapper.clientHeight;

    const sh = colorListWrapper.scrollHeight;

    const upH = customUpBtn.offsetHeight || 18;

    const downH = customDownBtn.offsetHeight || 18;

    const trackH = Math.max(0, Math.round(trackRect.height));

    const available = Math.max(0, trackH - upH - downH);

    if (sh <= ch || available <= 0) { customThumb.style.height = `${Math.max(0, available)}px`; customThumb.style.top = `${upH}px`; customThumb.style.display = sh <= ch ? 'none' : 'block'; return; }

    customThumb.style.display = 'block';

    const fractionVisible = ch / sh;

    const thumbH = Math.max(20, Math.round(available * fractionVisible));

    customThumb.style.height = `${thumbH}px`;

    const maxScroll = Math.max(0, sh - ch);

    const scrollTop = Math.max(0, Math.min(colorListWrapper.scrollTop, maxScroll));

    const movable = Math.max(0, available - thumbH);

    const frac = maxScroll > 0 ? (scrollTop / maxScroll) : 0;

    const topPos = Math.round(frac * movable);

    customThumb.style.top = `${upH + topPos}px`;

}

colorListWrapper.addEventListener('scroll', ()=>{ updateCustomThumb(); }, { passive: true });

customUpBtn.addEventListener('click', ()=>{ const delta = Math.round(colorListWrapper.clientHeight * 0.6) || 60; colorListWrapper.scrollBy({ top: -delta, behavior: 'smooth' }); });

customDownBtn.addEventListener('click', ()=>{ const delta = Math.round(colorListWrapper.clientHeight * 0.6) || 60; colorListWrapper.scrollBy({ top: delta, behavior: 'smooth' }); });

/* drag thumb */

let dragging = false, dragStartY = 0, startScrollTop = 0;

const getPointerClientY = (ev) => (typeof ev.clientY === 'number') ? ev.clientY : (ev.touches && ev.touches[0] && ev.touches[0].clientY) || 0;

const onPointerMoveWhileDragging = (ev) => {

    if (!dragging) return;

    ev.preventDefault && ev.preventDefault();

    const trackRect = customTrack.getBoundingClientRect(); const upH = customUpBtn.offsetHeight || 18; const downH = customDownBtn.offsetHeight || 18;

    const trackH = Math.max(0, Math.round(trackRect.height)); const available = Math.max(0, trackH - upH - downH);

    const sh = colorListWrapper.scrollHeight; const ch = colorListWrapper.clientHeight; const thumbH = customThumb.offsetHeight;

    const clientY = getPointerClientY(ev); const dy = clientY - dragStartY; const scrollable = Math.max(0, sh - ch);

    const movable = Math.max(1, Math.max(0, available - thumbH)); const deltaScroll = Math.round((dy / movable) * scrollable);

    colorListWrapper.scrollTop = Math.max(0, Math.min(startScrollTop + deltaScroll, scrollable));

};

const stopDragging = (ev) => {

    if (!dragging) return; dragging = false;

    try { document.removeEventListener('pointermove', onPointerMoveWhileDragging); document.removeEventListener('pointerup', stopDragging); document.removeEventListener('touchmove', onPointerMoveWhileDragging); document.removeEventListener('touchend', stopDragging); } catch (e) {}

};

customThumb.addEventListener('pointerdown', (ev) => { ev.preventDefault && ev.preventDefault(); dragging = true; dragStartY = ev.clientY || 0; startScrollTop = colorListWrapper.scrollTop; document.addEventListener('pointermove', onPointerMoveWhileDragging, { passive:false }); document.addEventListener('pointerup', stopDragging); });

customThumb.addEventListener('touchstart', (ev) => { ev.preventDefault && ev.preventDefault(); const t = ev.touches && ev.touches[0]; dragging = true; dragStartY = t ? t.clientY : 0; startScrollTop = colorListWrapper.scrollTop; document.addEventListener('touchmove', onPointerMoveWhileDragging, { passive:false }); document.addEventListener('touchend', stopDragging); });

/* Mutation observer para lista */

const listObserver = new MutationObserver(()=>{ needsLayoutUpdate = true; scheduleUIUpdate(); });

listObserver.observe(colorList, { childList: true, subtree: true });

function refreshChunks(){ /* placeholder */ }

/* =========================

   Min width dinâmico: mínimo = 1/3 da largura atual

   ========================= */

function ensureMinWidth() {

    try {

        const rect = colorStatsContainer.getBoundingClientRect();

        const curWidth = Math.max(rect.width || 220, 220);

        const minW = Math.round(curWidth / 3);

        colorStatsContainer.style.minWidth = `${minW}px`;

    } catch (e) {

        colorStatsContainer.style.minWidth = `${Math.round(220/3)}px`;

    }

}

/* =========================

   Visibilidade / minimizar-maximizar (mantém 30x30 quando minimizado)

   ========================= */

function applyVisibility() {

    if (listVisible) {

        colorStatsContainer.classList.remove('color-stats-collapsed');

        colorStatsContainer.style.minWidth = colorStatsContainer.style.minWidth || '220px';

        colorStatsContainer.style.maxWidth = '320px';

        colorStatsContainer.style.maxHeight = '420px';

        colorListWrapper.style.display = 'block'; controlsBox.style.display = 'flex'; divider.style.display = 'block'; title.style.display = 'block';

        minimizeBtn.classList.remove('btn-square-max'); minimizeBtn.classList.add('btn-square-min'); minimizeBtn.innerHTML = '-'; minimizeBtn.title = 'Minimizar'; header.classList.remove('minimized-centered');

    } else {

        colorStatsContainer.classList.add('color-stats-collapsed');

        colorListWrapper.style.display = 'none'; controlsBox.style.display = 'none'; divider.style.display = 'none'; title.style.display = 'none';

        minimizeBtn.classList.remove('btn-square-min'); minimizeBtn.classList.add('btn-square-max'); minimizeBtn.innerHTML = ''; minimizeBtn.title = 'Maximizar'; header.classList.add('minimized-centered');

    }

    ensureMinWidth();

    needsLayoutUpdate = true;

    scheduleUIUpdate();

}

minimizeBtn.addEventListener('click', (e)=>{ e.stopPropagation && e.stopPropagation(); listVisible = !listVisible; localStorage.setItem('overlay_color_list_collapsed', listVisible ? '0' : '1'); applyVisibility(); });

showAllCheckbox.addEventListener('change', (e)=>{ setAllSelected(e.target.checked); });

function setAllSelected(flag) {

    if (!flag) {

        selectedColors.clear(); showAll = false; initialSelectionDone = true; showAllCheckbox.checked = false; refreshChunks(); scheduleColorUIUpdate(lastColorCounts);

        return;

    }

    selectedColors.clear(); for (const key of Object.keys(lastColorCounts)) selectedColors.add(key); showAll = true; showAllCheckbox.checked = true; refreshChunks(); scheduleColorUIUpdate(lastColorCounts);

}

/* =========================

   FETCH PROXY: intercepta requests de chunks e despacha para worker

   ========================= */

fetch = new Proxy(fetch, {

    apply: async (target, thisArg, argList) => {

        const urlString = typeof argList[0] === "object" ? argList[0].url : argList[0];

        let url;

        try { url = new URL(urlString); } catch (e) { return target.apply(thisArg, argList); }

        if (overlayMode === "overlay") {

            if (url.hostname === "backend.wplace.live" && url.pathname.startsWith("/files/")) {

                for (const obj of overlays) {

                    if (url.pathname.endsWith(obj.chunksString)) {

                        const originalResponse = await target.apply(thisArg, argList);

                        const originalBlob = await originalResponse.blob();

                        const overlayCopy = obj.imageData.data.slice();

                        const overlayBuffer = overlayCopy.buffer;

                        const id = ++workerReqId;

                        const promise = new Promise((resolve, reject) => {

                            const timeout = setTimeout(()=>{ pendingWorker.delete(id); reject(new Error("Worker timed out")); }, 15000);

                            pendingWorker.set(id, {

                                resolve: (data)=>{ clearTimeout(timeout); resolve(data); },

                                reject: (err)=>{ clearTimeout(timeout); reject(err); }

                            });

                        });

                        pixelWorker.postMessage({

                            type: 'process',

                            id,

                            blob: originalBlob,

                            overlayBuffer,

                            overlayWidth: obj.imageData.width || CHUNK_WIDTH,

                            overlayHeight: obj.imageData.height || CHUNK_HEIGHT,

                            selectedColors: Array.from(selectedColors)

                        }, [overlayBuffer]);

                        let result;

                        try {

                            result = await promise;

                        } catch (err) {

                            console.error("Worker processing failed:", err);

                            return originalResponse;

                        }

                        scheduleColorUIUpdate(result.colorCounts || {}, result.wrongPixels, result.totalTargetPixels);

                        return new Response(result.blob, { headers: { "Content-Type": "image/png" } });

                    }

                }

            }

        } else if (overlayMode === "chunks") {

            if (url.hostname === "backend.wplace.live" && url.pathname.startsWith("/files/")) {

                const parts = url.pathname.split("/");

                const [chunk1, chunk2] = [parts.at(-2), parts.at(-1).split(".")[0]];

                const canvas = new OffscreenCanvas(CHUNK_WIDTH, CHUNK_HEIGHT);

                const ctx = canvas.getContext("2d", { willReadFrequently: true });

                ctx.strokeStyle = 'red'; ctx.lineWidth = 1; ctx.strokeRect(0, 0, CHUNK_WIDTH, CHUNK_HEIGHT);

                ctx.font = '30px Arial'; ctx.fillStyle = 'red'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';

                ctx.fillText(chunk1 + ", " + chunk2, CHUNK_WIDTH / 2, CHUNK_HEIGHT / 2);

                const mergedBlob = await canvas.convertToBlob();

                return new Response(mergedBlob, { headers: { "Content-Type": "image/png" } });

            }

        }

        return target.apply(thisArg, argList);

    }

});

/* =========================

   restante: load helpers, event handlers e patch UI

   ========================= */

/* observer para patch UI (botão de trocar overlayMode) */

function patchUI() {

    if (document.getElementById("overlay-blend-button")) return;

    let blendButton = document.createElement("button");

    blendButton.id = "overlay-blend-button";

    blendButton.textContent = overlayMode.charAt(0).toUpperCase() + overlayMode.slice(1);

    blendButton.style.backgroundColor = "#0e0e0e7f";

    blendButton.style.color = "white";

    blendButton.style.border = "solid"; blendButton.style.borderColor = "#1d1d1d7f";

    blendButton.style.borderRadius = "4px"; blendButton.style.padding = "5px 10px"; blendButton.style.cursor = "pointer";

    blendButton.style.backdropFilter = "blur(2px)";

    blendButton.addEventListener("click", () => {

        overlayMode = OVERLAY_MODES[(OVERLAY_MODES.indexOf(overlayMode) + 1) % OVERLAY_MODES.length];

        blendButton.textContent = overlayMode.charAt(0).toUpperCase() + overlayMode.slice(1);

    });

    const buttonContainer = document.querySelector("div.gap-4:nth-child(1) > div:nth-child(2)");

    if (buttonContainer) buttonContainer.appendChild(blendButton);

}

const observer = new MutationObserver(()=>{ patchUI(); });

try {

    const rootCandidate = document.querySelector("div.gap-4:nth-child(1)");

    observer.observe(rootCandidate || document.body, { childList: true, subtree: true });

} catch (e) { observer.observe(document.body, { childList: true, subtree: true }); }

patchUI();

/* colorNameMap de exemplo */

const colorNameMap = { "#000000":"Preto", "#3c3c3c":"Cinza-escuro", "#787878":"Cinza", "#aaaaaa":"Cinza-médio", "#d2d2d2":"Cinza-claro", "#ffffff":"Branco" };

/* =========================

   HELPERS ADICIONAIS (atalhos)

   ========================= */

function isEditableElement(el) {

    if (!el) return false;

    const tag = el.tagName && el.tagName.toUpperCase();

    if (tag === 'INPUT' || tag === 'TEXTAREA' || el.isContentEditable) return true;

    if (tag === 'SELECT') return true;

    return false;

}

function simulateKeyPress(keyChar) {

    try {

        const kd = new KeyboardEvent('keydown', { key: keyChar, code: `Key${keyChar.toUpperCase()}`, keyCode: keyChar.toUpperCase().charCodeAt(0), which: keyChar.toUpperCase().charCodeAt(0), bubbles: true, cancelable: true });

        const ku = new KeyboardEvent('keyup',   { key: keyChar, code: `Key${keyChar.toUpperCase()}`, keyCode: keyChar.toUpperCase().charCodeAt(0), which: keyChar.toUpperCase().charCodeAt(0), bubbles: true, cancelable: true });

        document.dispatchEvent(kd);

        setTimeout(()=>document.dispatchEvent(ku), 15);

    } catch (e) {

        try {

            document.dispatchEvent(new KeyboardEvent('keydown', { key: keyChar, bubbles: true }));

            setTimeout(()=>document.dispatchEvent(new KeyboardEvent('keyup', { key: keyChar, bubbles: true })), 15);

        } catch (ee) {}

    }

}

function isVisible(el) {

    if (!el) return false;

    if (!(el instanceof Element)) return false;

    const rect = el.getBoundingClientRect();

    if (rect.width <= 0 || rect.height <= 0) return false;

    const style = getComputedStyle(el);

    if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity || '1') === 0) return false;

    return true;

}

/* --------------------------

   findPaletteButton

   -------------------------- */

function findPaletteButton() {

    try {

        const nodes = Array.from(document.querySelectorAll('div.flex.items-center.gap-2'));

        const found = nodes.find(el => {

            if (!el) return false;

            const txt = (el.textContent || '').trim();

            return txt.length > 0 && txt.startsWith('Pintar');

        });

        if (!found) return null;

        const btn = found.querySelector('button') || found.querySelector('div[role="button"]') || found;

        return btn || found;

    } catch (e) { return null; }

}

/* =========================

   Bind dos novos atalhos:

   Middle click -> conta-gotas (svg.size-4.5)

   Enter -> toggle paleta (botão "Pintar")

   ========================= */

document.addEventListener('mousedown', function(e){

    try {

        if (e.button === 1) {

            if (isEditableElement(document.activeElement)) return;

            e.preventDefault && e.preventDefault();

            e.stopPropagation && e.stopPropagation();

            const svg = document.querySelector('svg.size-4\\.5');

            const target = svg ? (svg.closest('button') || svg.closest('div')) : null;

            if (target && isVisible(target)) {

                target.click();

                return;

            }

            const paintBtn = findPaletteButton();

            if (paintBtn) {

                paintBtn.click();

                setTimeout(()=>{

                    try {

                        const svg2 = document.querySelector('svg.size-4\\.5');

                        const target2 = svg2 ? (svg2.closest('button') || svg2.closest('div')) : null;

                        if (target2 && isVisible(target2)) target2.click();

                    } catch(_) {}

                }, 300);

            }

        }

    } catch (err) {}

}, { passive: false, capture: true });

document.addEventListener('keydown', function(e){

    try {

        if (e.key === 'Enter' || e.keyCode === 13) {

            if (isEditableElement(document.activeElement)) return;

            const btnContainer = findPaletteButton();

            if (!btnContainer) return;

            const btn = (btnContainer.tagName && btnContainer.tagName.toLowerCase() === 'button') ? btnContainer : (btnContainer.querySelector && (btnContainer.querySelector('button') || btnContainer.querySelector('div[role=\"button\"]')) ) || btnContainer;

            const openBtn = document.querySelector('button.btn.btn-lg.btn-square.sm\\:btn-xl.btn-primary');

            const isOpen = openBtn && isVisible(openBtn);

            e.preventDefault && e.preventDefault();

            e.stopPropagation && e.stopPropagation();

            try { btn.focus && btn.focus({preventScroll:true}); } catch(_) {}

            try { btn.click(); } catch(err) {}

            if (isOpen) {

                setTimeout(()=>{ try { btn.click(); } catch(e){} }, 220);

            }

        }

    } catch (err) {}

}, { passive: false, capture: true });

/* =========================

   ADIÇÃO: Long-press (1s) para ativar BORRACHA e desmarcar pixel (mobile)

   ========================= */

const LONG_PRESS_MS = 1000;

const LONG_PRESS_MOVE_TOLERANCE = 12;

let _lpTimer = null;

let _lpStartX = 0;

let _lpStartY = 0;

let _lpTarget = null;

function getEraserButtonByPath() {

    try {

        const candidates = Array.from(document.querySelectorAll('button.btn.btn-lg.btn-square.sm\\:btn-xl.shadow-md'));

        for (const b of candidates) {

            const path = b.querySelector('svg.size-5 path');

            const d = path && path.getAttribute && path.getAttribute('d');

            if (typeof d === 'string' && d.startsWith('M690')) {

                return b;

            }

        }

        const svgs = Array.from(document.querySelectorAll('svg.size-5'));

        for (const s of svgs) {

            const p = s.querySelector('path');

            const d = p && p.getAttribute && p.getAttribute('d');

            if (typeof d === 'string' && d.startsWith('M690')) {

                return s.closest('button') || s.closest('div') || null;

            }

        }

    } catch (e) {}

    return null;

}

function findCanvasAtPoint(x,y) {

    try {

        let el = document.elementFromPoint(x, y);

        if (el) {

            if (el.tagName && el.tagName.toLowerCase() === 'canvas') return el;

            const c = el.closest && el.closest('canvas');

            if (c && isVisible(c)) return c;

        }

        const canvases = Array.from(document.querySelectorAll('canvas')).filter(isVisible);

        for (const cv of canvases) {

            const r = cv.getBoundingClientRect();

            if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return cv;

        }

        if (canvases.length) {

            canvases.sort((a,b)=>{

                const ra = a.getBoundingClientRect(), rb = b.getBoundingClientRect();

                return (rb.width*rb.height) - (ra.width*ra.height);

            });

            return canvases[0];

        }

    } catch (e) {}

    return null;

}

async function dispatchPointerClickToCanvas(canvas, clientX, clientY) {

    if (!canvas) return false;

    const rect = canvas.getBoundingClientRect();

    const offsetX = clientX - rect.left;

    const offsetY = clientY - rect.top;

    try {

        const pd = new PointerEvent('pointerdown', { bubbles:true, cancelable:true, composed:true, pointerId:1, pointerType:'touch', clientX, clientY, offsetX, offsetY, pressure:0.5 });

        canvas.dispatchEvent(pd);

    } catch(e){}

    try { canvas.dispatchEvent(new MouseEvent('mousedown', { bubbles:true, cancelable:true, clientX, clientY, button:0 })); } catch(e){}

    try { await new Promise(r => setTimeout(r, 18)); } catch(e){}

    try { canvas.dispatchEvent(new PointerEvent('pointerup', { bubbles:true, cancelable:true, composed:true, pointerId:1, pointerType:'touch', clientX, clientY, offsetX, offsetY, pressure:0 })); } catch(e){}

    try { canvas.dispatchEvent(new MouseEvent('mouseup', { bubbles:true, cancelable:true, clientX, clientY, button:0 })); } catch(e){}

    try { canvas.dispatchEvent(new MouseEvent('click', { bubbles:true, cancelable:true, clientX, clientY, button:0 })); } catch(e){}

    return true;

}

function clearLongPressTimer() {

    if (_lpTimer) { clearTimeout(_lpTimer); _lpTimer = null; }

}

document.addEventListener('touchstart', function(e){

    try {

        if (!e.touches || !e.touches[0]) return;

        if (isEditableElement(document.activeElement)) return;

        if (colorStatsContainer && colorStatsContainer.contains(e.target)) return;

        const t = e.touches[0];

        _lpStartX = t.clientX;

        _lpStartY = t.clientY;

        _lpTarget = e.target;

        clearLongPressTimer();

        _lpTimer = setTimeout(async () => {

            if (globalTouch.isSwipe) { clearLongPressTimer(); return; }

            if (colorStatsContainer && colorStatsContainer.contains(_lpTarget)) { clearLongPressTimer(); return; }

            const eraserBtn = getEraserButtonByPath();

            if (!eraserBtn) { clearLongPressTimer(); return; }

            try { eraserBtn.click(); } catch(e){}

            await new Promise(r => setTimeout(r, 80));

            const canvas = findCanvasAtPoint(_lpStartX, _lpStartY);

            if (canvas) {

                try { await dispatchPointerClickToCanvas(canvas, _lpStartX, _lpStartY); } catch(e){}

            } else {

                try {

                    const el = document.elementFromPoint(_lpStartX, _lpStartY);

                    if (el && !colorStatsContainer.contains(el)) { try { el.click(); } catch(e){} }

                } catch(e){}

            }

            await new Promise(r => setTimeout(r, 90));

            try { eraserBtn.click(); } catch(e){}

            clearLongPressTimer();

        }, LONG_PRESS_MS);

    } catch (err) {

        clearLongPressTimer();

    }

}, { passive: true });

document.addEventListener('touchmove', function(e){

    try {

        if (!e.touches || !e.touches[0]) { clearLongPressTimer(); return; }

        const t = e.touches[0];

        const dx = Math.abs(t.clientX - _lpStartX);

        const dy = Math.abs(t.clientY - _lpStartY);

        if (dx > LONG_PRESS_MOVE_TOLERANCE || dy > LONG_PRESS_MOVE_TOLERANCE) {

            clearLongPressTimer();

        }

    } catch (err) {

        clearLongPressTimer();

    }

}, { passive: true });

document.addEventListener('touchend', function(e){

    clearLongPressTimer();

}, { passive: true });

/* =========================

   ADICIONEI AQUI: lógica de NOTIFICAÇÃO (Onça)

   - observe DOM (span.w-7.text-xs) para (0:00)

   - hook fillText para canvas [134x39] X/Y

   - only notify when evidence of timer (0:00) up to 2s before

   - avoid duplicates across tabs via localStorage

   ========================= */

/* estado notificação */

let onca_lastValue = "";

let onca_lastZeroTime = 0;

let onca_notifiedFor = null;

const ONCA_LOCAL_KEY = 'onca_notify_ts';

const ONCA_NOTIFY_DEBOUNCE_MS = 5000; // evita repetição entre abas

/* detecta (0:00) no DOM - observa mudanças */

const oncaDOMObserver = new MutationObserver(() => {

    const el = document.querySelector('span.w-7.text-xs');

    if (el) {

        const txt = (el.textContent || '').trim();

        if (txt === "(0:00)") {

            onca_lastZeroTime = Date.now();

        }

    }

});

try {

    oncaDOMObserver.observe(document.body, { childList: true, subtree: true, characterData: true });

} catch (e) { /* ignore */ }

/* função de envio de notificação (somente Notification API + prevenção de duplicatas entre abas) */

function onca_sendNotification() {

    try {

        // evitar notificações muito frequentes entre abas

        const now = Date.now();

        try {

            const last = parseInt(localStorage.getItem(ONCA_LOCAL_KEY) || '0', 10) || 0;

            if (now - last < ONCA_NOTIFY_DEBOUNCE_MS) return; // outra aba já notificou recentemente

            localStorage.setItem(ONCA_LOCAL_KEY, String(now));

        } catch (e) { /* ignore localStorage errors */ }

        const title = "Hora da onça beber água!";

        const opt = {

            body: "Tinta totalmente recarregada! 🔥",

            icon: "https://i.imgur.com/mz6eWey.png",

            timestamp: Date.now()

        };

        if (Notification.permission === "granted") {

            new Notification(title, opt);

        } else if (Notification.permission !== "denied") {

            Notification.requestPermission().then(permission => {

                if (permission === "granted") {

                    new Notification(title, opt);

                }

            }).catch(()=>{});

        }

    } catch (e) {

        // swallow

    }

}

/* hook fillText para detectar X/Y no canvas alvo */

const _origFillTextOnca = CanvasRenderingContext2D.prototype.fillText;

CanvasRenderingContext2D.prototype.fillText = function (text, x, y, maxWidth) {

    try {

        const canvas = this.canvas;

        if (!canvas) return _origFillTextOnca.apply(this, arguments);

        const dims = `[${canvas.width}x${canvas.height}]`;

        const txt = String(text).trim();

        // Verifica canvas do X/Y

        if (dims === "[134x39]" && /^\d+\/\d+$/.test(txt)) {

            const [curr, max] = txt.split("/").map(n => parseInt(n,10));

            const [lastCurr, lastMax] = onca_lastValue ? onca_lastValue.split("/").map(n => parseInt(n,10)) : [null, null];

            // detectar transição para full

            if (lastCurr !== null && lastCurr < lastMax && curr === max) {

                const diff = Date.now() - onca_lastZeroTime;

                if (diff >= 0 && diff <= 2000) {

                    // evitar notificar repetidamente para o mesmo valor

                    const keyFor = `${curr}/${max}@${canvas.width}x${canvas.height}`;

                    if (onca_notifiedFor !== keyFor) {

                        onca_notifiedFor = keyFor;

                        onca_sendNotification();

                    }

                }

            }

            // reset notified flag se voltar a gastar tinta

            if (curr < max) {

                onca_notifiedFor = null;

            }

            onca_lastValue = txt;

        }

    } catch (e) {

        // swallow

    }

    return _origFillTextOnca.apply(this, arguments);

};

/* =========================

   restante final: patch UI, inicialização e fallback

   ========================= */

/* observer para patch UI já definido acima */

ensureMinWidth();

applyVisibility();

/* fim do script */

})();