// ==UserScript==
// @name Wplace Dark Mode
// @namespace awoone.scripts
// @match https://wplace.live/*
// @grant none
// @version 1.2.0
// @author awo
// @description Dark mode do mapa sem sobrescrever window.fetch (não conflita com outros scripts).
// @run-at document-start
// @license MIT
// ==/UserScript==
(() => {
"use strict";
const MAP_STYLE_URL = "https://maps.wplace.live/styles/liberty";
const MODE_KEY = "wplace-darkmode-mode";
const storage = {
get: (key, def = null) => {
try {
return localStorage.getItem(key) ?? def;
} catch {
return def;
}
},
set: (key, val) => {
try {
localStorage.setItem(key, val);
} catch {}
},
};
const getMode = () => storage.get(MODE_KEY, "dark");
const isDark = () => getMode() === "dark";
function applyThemeVars() {
const dark = isDark();
const id = "wplace-dm-rootvars";
let style = document.getElementById(id);
if (!style) {
style = document.createElement("style");
style.id = id;
document.head.appendChild(style);
}
style.textContent = dark
? `
:root {
--color-base-100: #1b1e24;
--color-base-200: #262b36;
--color-base-300: #151922;
--color-base-content: #f5f6f9;
--noise: 0;
}
#color-0 { background-color: white !important; }
`
: `
:root {
--color-base-100: #ffffff;
--color-base-200: #f0f0f0;
--color-base-300: #e0e0e0;
--color-base-content: #000000;
--noise: 0;
}
`;
}
const originalFetch = window.fetch;
window.fetch = async (req, options) => {
const res = await originalFetch(req, options);
if (!isDark() || res.url !== MAP_STYLE_URL) return res;
try {
const json = await res.json();
const patched = applyLibertyDarkTheme(json);
return new Response(JSON.stringify(patched), {
headers: res.headers,
status: res.status,
statusText: res.statusText,
});
} catch {
return res;
}
};
function applyLibertyDarkTheme(styleObj) {
if (!styleObj?.layers) return styleObj;
for (const layer of styleObj.layers) {
const p = layer.paint || (layer.paint = {});
switch (layer.id) {
case "background":
p["background-color"] = "#272e40";
break;
case "water":
p["fill-color"] = "#000d2a";
break;
case "waterway_tunnel":
case "waterway_river":
case "waterway_other":
p["line-color"] = "#000d2a";
break;
case "natural_earth":
p["raster-brightness-max"] = 0.4;
break;
case "landcover_ice":
p["fill-color"] = "#475677";
break;
case "landcover_sand":
p["fill-color"] = "#775f47";
break;
case "park":
layer.paint = { "fill-color": "#0e4957", "fill-opacity": 0.7 };
break;
case "park_outline":
p["line-opacity"] = 0;
break;
case "landuse_pitch":
case "landuse_track":
case "landuse_school":
p["fill-color"] = "#3e4966";
break;
case "landuse_cemetery":
p["fill-color"] = "#3b3b57";
break;
case "landuse_hospital":
p["fill-color"] = "#663e3e";
break;
case "building":
p["fill-color"] = "#1c3b69";
break;
case "building_3d":
p["fill-extrusion-color"] = "#1c3b69";
break;
case "waterway_line_label":
case "water_name_point_label":
case "water_name_line_label":
p["text-color"] = "#8bb6f8";
p["text-halo-color"] = "rgba(0,0,0,0.7)";
break;
case "tunnel_path_pedestrian":
case "road_path_pedestrian":
case "bridge_path_pedestrian":
p["line-color"] = "#7c8493";
break;
case "bridge_path_pedestrian_casing":
p["line-color"] = "#3b4d65";
break;
case "road_minor":
case "tunnel_service_track":
case "tunnel_minor":
case "road_service_track":
case "bridge_service_track":
case "bridge_street":
p["line-color"] = "#3b4d65";
break;
case "tunnel_link":
case "tunnel_secondary_tertiary":
case "tunnel_trunk_primary":
case "tunnel_motorway":
p["line-color"] = "#4a627e";
break;
case "label_other":
case "label_state":
case "poi_r20":
case "poi_r7":
case "poi_r1":
case "highway_name_minor":
p["text-color"] = "#91a0b5";
p["text-halo-color"] = "rgba(0,0,0,0.7)";
break;
case "poi_transit":
case "highway_name_path":
case "highway_name_major":
p["text-color"] = "#cde0fe";
p["text-halo-color"] = "rgba(0,0,0,0.7)";
break;
case "label_village":
case "label_town":
case "label_city":
case "label_city_capital":
case "label_country_3":
case "label_country_2":
case "label_country_1":
p["text-color"] = "#e4e5e9";
p["text-halo-color"] = "rgba(0,0,0,0.7)";
break;
case "airport":
p["text-color"] = "#92b7fe";
p["text-halo-color"] = "rgba(0,0,0,0.7)";
break;
case "aeroway_fill":
p["fill-color"] = "#2a486c";
break;
case "aeroway_runway":
p["line-color"] = "#253d61";
break;
case "aeroway_taxiway":
p["line-color"] = "#3d5b77";
break;
case "boundary_3":
p["line-color"] = "#707784";
break;
}
}
return JSON.parse(
JSON.stringify(styleObj)
.replaceAll("#e9ac77", "#476889")
.replaceAll("#fc8", "#476889")
.replaceAll("#fea", "#3d5b77")
.replaceAll("#cfcdca", "#3b4d65")
);
}
function updateBtnIcon(btn, mode) {
btn.innerHTML =
mode === "dark"
? `<svg width="24px" height="24px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30.457 30.457" fill="currentColor" class="size-3.5"><path d="M29.693,14.49c-0.469-0.174-1-0.035-1.32,0.353c-1.795,2.189-4.443,3.446-7.27,3.446c-5.183,0-9.396-4.216-9.396-9.397c0-2.608,1.051-5.036,2.963-6.835c0.366-0.347,0.471-0.885,0.264-1.343c-0.207-0.456-0.682-0.736-1.184-0.684C5.91,0.791,0,7.311,0,15.194c0,8.402,6.836,15.238,15.238,15.238c8.303,0,14.989-6.506,15.219-14.812C30.471,15.118,30.164,14.664,29.693,14.49z"/></svg>`
: `<svg width="24px" height="24px" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 12.5H22M3 12.5H6M12.5 6V3M12.5 22V19M17.3891 7.61091L19.5104 5.48959M5.48959 19.5104L7.61091 17.3891M7.61091 7.61091L5.48959 5.48959M19.5104 19.5104L17.3891 17.3891M16 12.5C16 14.433 14.433 16 12.5 16C10.567 16 9 14.433 9 12.5C9 10.567 10.567 9 12.5 9C14.433 9 16 10.567 16 12.5Z" stroke="#121923" stroke-width="1.2"/></svg>`;
}
function injectBtn(container) {
if (!container || container.querySelector("#darkmode-toggle-btn")) return;
const btn = document.createElement("button");
btn.title = "Dark/Light";
btn.className = "btn btn-sm btn-circle";
btn.id = "darkmode-toggle-btn";
updateBtnIcon(btn, getMode());
btn.addEventListener("click", () => {
storage.set(MODE_KEY, isDark() ? "light" : "dark");
location.reload();
});
const wrapper = document.createElement("div");
wrapper.className = "indicator";
wrapper.appendChild(btn);
container.appendChild(wrapper);
}
function observeContainer() {
const selector = ".absolute.left-2.top-2.flex-col";
const container = document.querySelector(selector);
if (container) injectBtn(container);
new MutationObserver(() => {
const target = document.querySelector(selector);
if (target) injectBtn(target);
}).observe(document.body, { childList: true, subtree: true });
}
applyThemeVars();
document.addEventListener("DOMContentLoaded", observeContainer);
})();