// ==UserScript==
// @name Internet Roadtrip - Thermometer
// @description Display a thermometer widget with the temperature, time, and humidity of the current location of the neal.fun/internet-roadtrip vehicle
// @namespace me.netux.site/user-scripts/internet-roadtrip/thermometer
// @icon https://cloudy.netux.site/neal_internet_roadtrip/Thermometer%20logo.png
// @match https://neal.fun/*
// @author Netux
// @require https://cdn.jsdelivr.net/npm/[email protected]
// @require https://cdn.jsdelivr.net/npm/@violentmonkey/[email protected]
// @grant GM.deleteValue
// @grant GM.getValue
// @grant GM.info
// @grant GM.setValue
// @grant GM.xmlHttpRequest
// @grant GM_addStyle
// @grant GM_fetch
// @version 2.0.0
// ==/UserScript==
(async function (IRF, dom) {
'use strict';
const IS_DEV = false;
const equalFn = (a, b) => a === b;
const $PROXY = Symbol("solid-proxy");
const SUPPORTS_PROXY = typeof Proxy === "function";
const $TRACK = Symbol("solid-track");
const signalOptions = {
equals: equalFn
};
let runEffects = runQueue;
const STALE = 1;
const PENDING = 2;
const UNOWNED = {
owned: null,
cleanups: null,
context: null,
owner: null
};
var Owner = null;
let Transition = null;
let ExternalSourceConfig = null;
let Listener = null;
let Updates = null;
let Effects = null;
let ExecCount = 0;
function createRoot(fn, detachedOwner) {
const listener = Listener,
owner = Owner,
unowned = fn.length === 0,
current = detachedOwner === undefined ? owner : detachedOwner,
root = unowned ? UNOWNED : {
owned: null,
cleanups: null,
context: current ? current.context : null,
owner: current
},
updateFn = unowned ? fn : () => fn(() => untrack(() => cleanNode(root)));
Owner = root;
Listener = null;
try {
return runUpdates(updateFn, true);
} finally {
Listener = listener;
Owner = owner;
}
}
function createSignal(value, options) {
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
const s = {
value,
observers: null,
observerSlots: null,
comparator: options.equals || undefined
};
const setter = value => {
if (typeof value === "function") {
value = value(s.value);
}
return writeSignal(s, value);
};
return [readSignal.bind(s), setter];
}
function createRenderEffect(fn, value, options) {
const c = createComputation(fn, value, false, STALE);
updateComputation(c);
}
function createEffect(fn, value, options) {
runEffects = runUserEffects;
const c = createComputation(fn, value, false, STALE);
if (!options || !options.render) c.user = true;
Effects ? Effects.push(c) : updateComputation(c);
}
function createMemo(fn, value, options) {
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
const c = createComputation(fn, value, true, 0);
c.observers = null;
c.observerSlots = null;
c.comparator = options.equals || undefined;
updateComputation(c);
return readSignal.bind(c);
}
function untrack(fn) {
if (Listener === null) return fn();
const listener = Listener;
Listener = null;
try {
if (ExternalSourceConfig) ;
return fn();
} finally {
Listener = listener;
}
}
function onMount(fn) {
createEffect(() => untrack(fn));
}
function onCleanup(fn) {
if (Owner === null) ;else if (Owner.cleanups === null) Owner.cleanups = [fn];else Owner.cleanups.push(fn);
return fn;
}
function getOwner() {
return Owner;
}
function runWithOwner(o, fn) {
const prev = Owner;
const prevListener = Listener;
Owner = o;
Listener = null;
try {
return runUpdates(fn, true);
} catch (err) {
handleError(err);
} finally {
Owner = prev;
Listener = prevListener;
}
}
function createContext(defaultValue, options) {
const id = Symbol("context");
return {
id,
Provider: createProvider(id),
defaultValue
};
}
function useContext(context) {
let value;
return Owner && Owner.context && (value = Owner.context[context.id]) !== undefined ? value : context.defaultValue;
}
function children(fn) {
const children = createMemo(fn);
const memo = createMemo(() => resolveChildren(children()));
memo.toArray = () => {
const c = memo();
return Array.isArray(c) ? c : c != null ? [c] : [];
};
return memo;
}
function readSignal() {
if (this.sources && (this.state)) {
if ((this.state) === STALE) updateComputation(this);else {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(this), false);
Updates = updates;
}
}
if (Listener) {
const sSlot = this.observers ? this.observers.length : 0;
if (!Listener.sources) {
Listener.sources = [this];
Listener.sourceSlots = [sSlot];
} else {
Listener.sources.push(this);
Listener.sourceSlots.push(sSlot);
}
if (!this.observers) {
this.observers = [Listener];
this.observerSlots = [Listener.sources.length - 1];
} else {
this.observers.push(Listener);
this.observerSlots.push(Listener.sources.length - 1);
}
}
return this.value;
}
function writeSignal(node, value, isComp) {
let current = node.value;
if (!node.comparator || !node.comparator(current, value)) {
node.value = value;
if (node.observers && node.observers.length) {
runUpdates(() => {
for (let i = 0; i < node.observers.length; i += 1) {
const o = node.observers[i];
const TransitionRunning = Transition && Transition.running;
if (TransitionRunning && Transition.disposed.has(o)) ;
if (TransitionRunning ? !o.tState : !o.state) {
if (o.pure) Updates.push(o);else Effects.push(o);
if (o.observers) markDownstream(o);
}
if (!TransitionRunning) o.state = STALE;
}
if (Updates.length > 10e5) {
Updates = [];
if (IS_DEV) ;
throw new Error();
}
}, false);
}
}
return value;
}
function updateComputation(node) {
if (!node.fn) return;
cleanNode(node);
const time = ExecCount;
runComputation(node, node.value, time);
}
function runComputation(node, value, time) {
let nextValue;
const owner = Owner,
listener = Listener;
Listener = Owner = node;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) {
{
node.state = STALE;
node.owned && node.owned.forEach(cleanNode);
node.owned = null;
}
}
node.updatedAt = time + 1;
return handleError(err);
} finally {
Listener = listener;
Owner = owner;
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in node) {
writeSignal(node, nextValue);
} else node.value = nextValue;
node.updatedAt = time;
}
}
function createComputation(fn, init, pure, state = STALE, options) {
const c = {
fn,
state: state,
updatedAt: null,
owned: null,
sources: null,
sourceSlots: null,
cleanups: null,
value: init,
owner: Owner,
context: Owner ? Owner.context : null,
pure
};
if (Owner === null) ;else if (Owner !== UNOWNED) {
{
if (!Owner.owned) Owner.owned = [c];else Owner.owned.push(c);
}
}
return c;
}
function runTop(node) {
if ((node.state) === 0) return;
if ((node.state) === PENDING) return lookUpstream(node);
if (node.suspense && untrack(node.suspense.inFallback)) return node.suspense.effects.push(node);
const ancestors = [node];
while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) {
if (node.state) ancestors.push(node);
}
for (let i = ancestors.length - 1; i >= 0; i--) {
node = ancestors[i];
if ((node.state) === STALE) {
updateComputation(node);
} else if ((node.state) === PENDING) {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(node, ancestors[0]), false);
Updates = updates;
}
}
}
function runUpdates(fn, init) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!wait) Effects = null;
Updates = null;
handleError(err);
}
}
function completeUpdates(wait) {
if (Updates) {
runQueue(Updates);
Updates = null;
}
if (wait) return;
const e = Effects;
Effects = null;
if (e.length) runUpdates(() => runEffects(e), false);
}
function runQueue(queue) {
for (let i = 0; i < queue.length; i++) runTop(queue[i]);
}
function runUserEffects(queue) {
let i,
userLength = 0;
for (i = 0; i < queue.length; i++) {
const e = queue[i];
if (!e.user) runTop(e);else queue[userLength++] = e;
}
for (i = 0; i < userLength; i++) runTop(queue[i]);
}
function lookUpstream(node, ignore) {
node.state = 0;
for (let i = 0; i < node.sources.length; i += 1) {
const source = node.sources[i];
if (source.sources) {
const state = source.state;
if (state === STALE) {
if (source !== ignore && (!source.updatedAt || source.updatedAt < ExecCount)) runTop(source);
} else if (state === PENDING) lookUpstream(source, ignore);
}
}
}
function markDownstream(node) {
for (let i = 0; i < node.observers.length; i += 1) {
const o = node.observers[i];
if (!o.state) {
o.state = PENDING;
if (o.pure) Updates.push(o);else Effects.push(o);
o.observers && markDownstream(o);
}
}
}
function cleanNode(node) {
let i;
if (node.sources) {
while (node.sources.length) {
const source = node.sources.pop(),
index = node.sourceSlots.pop(),
obs = source.observers;
if (obs && obs.length) {
const n = obs.pop(),
s = source.observerSlots.pop();
if (index < obs.length) {
n.sourceSlots[s] = index;
obs[index] = n;
source.observerSlots[index] = s;
}
}
}
}
if (node.tOwned) {
for (i = node.tOwned.length - 1; i >= 0; i--) cleanNode(node.tOwned[i]);
delete node.tOwned;
}
if (node.owned) {
for (i = node.owned.length - 1; i >= 0; i--) cleanNode(node.owned[i]);
node.owned = null;
}
if (node.cleanups) {
for (i = node.cleanups.length - 1; i >= 0; i--) node.cleanups[i]();
node.cleanups = null;
}
node.state = 0;
}
function castError(err) {
if (err instanceof Error) return err;
return new Error(typeof err === "string" ? err : "Unknown error", {
cause: err
});
}
function handleError(err, owner = Owner) {
const error = castError(err);
throw error;
}
function resolveChildren(children) {
if (typeof children === "function" && !children.length) return resolveChildren(children());
if (Array.isArray(children)) {
const results = [];
for (let i = 0; i < children.length; i++) {
const result = resolveChildren(children[i]);
Array.isArray(result) ? results.push.apply(results, result) : results.push(result);
}
return results;
}
return children;
}
function createProvider(id, options) {
return function provider(props) {
let res;
createRenderEffect(() => res = untrack(() => {
Owner.context = {
...Owner.context,
[id]: props.value
};
return children(() => props.children);
}), undefined);
return res;
};
}
const FALLBACK = Symbol("fallback");
function dispose(d) {
for (let i = 0; i < d.length; i++) d[i]();
}
function mapArray(list, mapFn, options = {}) {
let items = [],
mapped = [],
disposers = [],
len = 0,
indexes = mapFn.length > 1 ? [] : null;
onCleanup(() => dispose(disposers));
return () => {
let newItems = list() || [],
newLen = newItems.length,
i,
j;
newItems[$TRACK];
return untrack(() => {
let newIndices, newIndicesNext, temp, tempdisposers, tempIndexes, start, end, newEnd, item;
if (newLen === 0) {
if (len !== 0) {
dispose(disposers);
disposers = [];
items = [];
mapped = [];
len = 0;
indexes && (indexes = []);
}
if (options.fallback) {
items = [FALLBACK];
mapped[0] = createRoot(disposer => {
disposers[0] = disposer;
return options.fallback();
});
len = 1;
}
}
else if (len === 0) {
mapped = new Array(newLen);
for (j = 0; j < newLen; j++) {
items[j] = newItems[j];
mapped[j] = createRoot(mapper);
}
len = newLen;
} else {
temp = new Array(newLen);
tempdisposers = new Array(newLen);
indexes && (tempIndexes = new Array(newLen));
for (start = 0, end = Math.min(len, newLen); start < end && items[start] === newItems[start]; start++);
for (end = len - 1, newEnd = newLen - 1; end >= start && newEnd >= start && items[end] === newItems[newEnd]; end--, newEnd--) {
temp[newEnd] = mapped[end];
tempdisposers[newEnd] = disposers[end];
indexes && (tempIndexes[newEnd] = indexes[end]);
}
newIndices = new Map();
newIndicesNext = new Array(newEnd + 1);
for (j = newEnd; j >= start; j--) {
item = newItems[j];
i = newIndices.get(item);
newIndicesNext[j] = i === undefined ? -1 : i;
newIndices.set(item, j);
}
for (i = start; i <= end; i++) {
item = items[i];
j = newIndices.get(item);
if (j !== undefined && j !== -1) {
temp[j] = mapped[i];
tempdisposers[j] = disposers[i];
indexes && (tempIndexes[j] = indexes[i]);
j = newIndicesNext[j];
newIndices.set(item, j);
} else disposers[i]();
}
for (j = start; j < newLen; j++) {
if (j in temp) {
mapped[j] = temp[j];
disposers[j] = tempdisposers[j];
if (indexes) {
indexes[j] = tempIndexes[j];
indexes[j](j);
}
} else mapped[j] = createRoot(mapper);
}
mapped = mapped.slice(0, len = newLen);
items = newItems.slice(0);
}
return mapped;
});
function mapper(disposer) {
disposers[j] = disposer;
if (indexes) {
const [s, set] = createSignal(j);
indexes[j] = set;
return mapFn(newItems[j], s);
}
return mapFn(newItems[j]);
}
};
}
function indexArray(list, mapFn, options = {}) {
let items = [],
mapped = [],
disposers = [],
signals = [],
len = 0,
i;
onCleanup(() => dispose(disposers));
return () => {
const newItems = list() || [],
newLen = newItems.length;
newItems[$TRACK];
return untrack(() => {
if (newLen === 0) {
if (len !== 0) {
dispose(disposers);
disposers = [];
items = [];
mapped = [];
len = 0;
signals = [];
}
if (options.fallback) {
items = [FALLBACK];
mapped[0] = createRoot(disposer => {
disposers[0] = disposer;
return options.fallback();
});
len = 1;
}
return mapped;
}
if (items[0] === FALLBACK) {
disposers[0]();
disposers = [];
items = [];
mapped = [];
len = 0;
}
for (i = 0; i < newLen; i++) {
if (i < items.length && items[i] !== newItems[i]) {
signals[i](() => newItems[i]);
} else if (i >= items.length) {
mapped[i] = createRoot(mapper);
}
}
for (; i < items.length; i++) {
disposers[i]();
}
len = signals.length = disposers.length = newLen;
items = newItems.slice(0);
return mapped = mapped.slice(0, len);
});
function mapper(disposer) {
disposers[i] = disposer;
const [s, set] = createSignal(newItems[i]);
signals[i] = set;
return mapFn(s, i);
}
};
}
function createComponent(Comp, props) {
return untrack(() => Comp(props || {}));
}
function trueFn() {
return true;
}
const propTraps = {
get(_, property, receiver) {
if (property === $PROXY) return receiver;
return _.get(property);
},
has(_, property) {
if (property === $PROXY) return true;
return _.has(property);
},
set: trueFn,
deleteProperty: trueFn,
getOwnPropertyDescriptor(_, property) {
return {
configurable: true,
enumerable: true,
get() {
return _.get(property);
},
set: trueFn,
deleteProperty: trueFn
};
},
ownKeys(_) {
return _.keys();
}
};
function resolveSource(s) {
return !(s = typeof s === "function" ? s() : s) ? {} : s;
}
function resolveSources() {
for (let i = 0, length = this.length; i < length; ++i) {
const v = this[i]();
if (v !== undefined) return v;
}
}
function mergeProps(...sources) {
let proxy = false;
for (let i = 0; i < sources.length; i++) {
const s = sources[i];
proxy = proxy || !!s && $PROXY in s;
sources[i] = typeof s === "function" ? (proxy = true, createMemo(s)) : s;
}
if (SUPPORTS_PROXY && proxy) {
return new Proxy({
get(property) {
for (let i = sources.length - 1; i >= 0; i--) {
const v = resolveSource(sources[i])[property];
if (v !== undefined) return v;
}
},
has(property) {
for (let i = sources.length - 1; i >= 0; i--) {
if (property in resolveSource(sources[i])) return true;
}
return false;
},
keys() {
const keys = [];
for (let i = 0; i < sources.length; i++) keys.push(...Object.keys(resolveSource(sources[i])));
return [...new Set(keys)];
}
}, propTraps);
}
const sourcesMap = {};
const defined = Object.create(null);
for (let i = sources.length - 1; i >= 0; i--) {
const source = sources[i];
if (!source) continue;
const sourceKeys = Object.getOwnPropertyNames(source);
for (let i = sourceKeys.length - 1; i >= 0; i--) {
const key = sourceKeys[i];
if (key === "__proto__" || key === "constructor") continue;
const desc = Object.getOwnPropertyDescriptor(source, key);
if (!defined[key]) {
defined[key] = desc.get ? {
enumerable: true,
configurable: true,
get: resolveSources.bind(sourcesMap[key] = [desc.get.bind(source)])
} : desc.value !== undefined ? desc : undefined;
} else {
const sources = sourcesMap[key];
if (sources) {
if (desc.get) sources.push(desc.get.bind(source));else if (desc.value !== undefined) sources.push(() => desc.value);
}
}
}
}
const target = {};
const definedKeys = Object.keys(defined);
for (let i = definedKeys.length - 1; i >= 0; i--) {
const key = definedKeys[i],
desc = defined[key];
if (desc && desc.get) Object.defineProperty(target, key, desc);else target[key] = desc ? desc.value : undefined;
}
return target;
}
function splitProps(props, ...keys) {
if (SUPPORTS_PROXY && $PROXY in props) {
const blocked = new Set(keys.length > 1 ? keys.flat() : keys[0]);
const res = keys.map(k => {
return new Proxy({
get(property) {
return k.includes(property) ? props[property] : undefined;
},
has(property) {
return k.includes(property) && property in props;
},
keys() {
return k.filter(property => property in props);
}
}, propTraps);
});
res.push(new Proxy({
get(property) {
return blocked.has(property) ? undefined : props[property];
},
has(property) {
return blocked.has(property) ? false : property in props;
},
keys() {
return Object.keys(props).filter(k => !blocked.has(k));
}
}, propTraps));
return res;
}
const otherObject = {};
const objects = keys.map(() => ({}));
for (const propName of Object.getOwnPropertyNames(props)) {
const desc = Object.getOwnPropertyDescriptor(props, propName);
const isDefaultDesc = !desc.get && !desc.set && desc.enumerable && desc.writable && desc.configurable;
let blocked = false;
let objectIndex = 0;
for (const k of keys) {
if (k.includes(propName)) {
blocked = true;
isDefaultDesc ? objects[objectIndex][propName] = desc.value : Object.defineProperty(objects[objectIndex], propName, desc);
}
++objectIndex;
}
if (!blocked) {
isDefaultDesc ? otherObject[propName] = desc.value : Object.defineProperty(otherObject, propName, desc);
}
}
return [...objects, otherObject];
}
const narrowedError = name => `Stale read from <${name}>.`;
function For(props) {
const fallback = "fallback" in props && {
fallback: () => props.fallback
};
return createMemo(mapArray(() => props.each, props.children, fallback || undefined));
}
function Index(props) {
const fallback = "fallback" in props && {
fallback: () => props.fallback
};
return createMemo(indexArray(() => props.each, props.children, fallback || undefined));
}
function Show(props) {
const keyed = props.keyed;
const conditionValue = createMemo(() => props.when, undefined, undefined);
const condition = keyed ? conditionValue : createMemo(conditionValue, undefined, {
equals: (a, b) => !a === !b
});
return createMemo(() => {
const c = condition();
if (c) {
const child = props.children;
const fn = typeof child === "function" && child.length > 0;
return fn ? untrack(() => child(keyed ? c : () => {
if (!untrack(condition)) throw narrowedError("Show");
return conditionValue();
})) : child;
}
return props.fallback;
}, undefined, undefined);
}
const booleans = ["allowfullscreen", "async", "alpha",
"autofocus",
"autoplay", "checked", "controls", "default", "disabled", "formnovalidate", "hidden",
"indeterminate", "inert",
"ismap", "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required", "reversed", "seamless",
"selected", "adauctionheaders",
"browsingtopics",
"credentialless",
"defaultchecked", "defaultmuted", "defaultselected", "defer", "disablepictureinpicture", "disableremoteplayback", "preservespitch",
"shadowrootclonable", "shadowrootcustomelementregistry",
"shadowrootdelegatesfocus", "shadowrootserializable",
"sharedstoragewritable"
];
const Properties = /*#__PURE__*/new Set([
"className", "value",
"readOnly", "noValidate", "formNoValidate", "isMap", "noModule", "playsInline", "adAuctionHeaders",
"allowFullscreen", "browsingTopics",
"defaultChecked", "defaultMuted", "defaultSelected", "disablePictureInPicture", "disableRemotePlayback", "preservesPitch", "shadowRootClonable", "shadowRootCustomElementRegistry",
"shadowRootDelegatesFocus", "shadowRootSerializable",
"sharedStorageWritable",
...booleans]);
const ChildProperties = /*#__PURE__*/new Set(["innerHTML", "textContent", "innerText", "children"]);
const Aliases = /*#__PURE__*/Object.assign(Object.create(null), {
className: "class",
htmlFor: "for"
});
const PropAliases = /*#__PURE__*/Object.assign(Object.create(null), {
class: "className",
novalidate: {
$: "noValidate",
FORM: 1
},
formnovalidate: {
$: "formNoValidate",
BUTTON: 1,
INPUT: 1
},
ismap: {
$: "isMap",
IMG: 1
},
nomodule: {
$: "noModule",
SCRIPT: 1
},
playsinline: {
$: "playsInline",
VIDEO: 1
},
readonly: {
$: "readOnly",
INPUT: 1,
TEXTAREA: 1
},
adauctionheaders: {
$: "adAuctionHeaders",
IFRAME: 1
},
allowfullscreen: {
$: "allowFullscreen",
IFRAME: 1
},
browsingtopics: {
$: "browsingTopics",
IMG: 1
},
defaultchecked: {
$: "defaultChecked",
INPUT: 1
},
defaultmuted: {
$: "defaultMuted",
AUDIO: 1,
VIDEO: 1
},
defaultselected: {
$: "defaultSelected",
OPTION: 1
},
disablepictureinpicture: {
$: "disablePictureInPicture",
VIDEO: 1
},
disableremoteplayback: {
$: "disableRemotePlayback",
AUDIO: 1,
VIDEO: 1
},
preservespitch: {
$: "preservesPitch",
AUDIO: 1,
VIDEO: 1
},
shadowrootclonable: {
$: "shadowRootClonable",
TEMPLATE: 1
},
shadowrootdelegatesfocus: {
$: "shadowRootDelegatesFocus",
TEMPLATE: 1
},
shadowrootserializable: {
$: "shadowRootSerializable",
TEMPLATE: 1
},
sharedstoragewritable: {
$: "sharedStorageWritable",
IFRAME: 1,
IMG: 1
}
});
function getPropAlias(prop, tagName) {
const a = PropAliases[prop];
return typeof a === "object" ? a[tagName] ? a["$"] : undefined : a;
}
const DelegatedEvents = /*#__PURE__*/new Set(["beforeinput", "click", "dblclick", "contextmenu", "focusin", "focusout", "input", "keydown", "keyup", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup", "pointerdown", "pointermove", "pointerout", "pointerover", "pointerup", "touchend", "touchmove", "touchstart"]);
const SVGElements = /*#__PURE__*/new Set([
"altGlyph", "altGlyphDef", "altGlyphItem", "animate", "animateColor", "animateMotion", "animateTransform", "circle", "clipPath", "color-profile", "cursor", "defs", "desc", "ellipse", "feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence", "filter", "font", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignObject", "g", "glyph", "glyphRef", "hkern", "image", "line", "linearGradient", "marker", "mask", "metadata", "missing-glyph", "mpath", "path", "pattern", "polygon", "polyline", "radialGradient", "rect",
"set", "stop",
"svg", "switch", "symbol", "text", "textPath",
"tref", "tspan", "use", "view", "vkern"]);
const SVGNamespace = {
xlink: "http://www.w3.org/1999/xlink",
xml: "http://www.w3.org/XML/1998/namespace"
};
const memo = fn => createMemo(() => fn());
function reconcileArrays(parentNode, a, b) {
let bLength = b.length,
aEnd = a.length,
bEnd = bLength,
aStart = 0,
bStart = 0,
after = a[aEnd - 1].nextSibling,
map = null;
while (aStart < aEnd || bStart < bEnd) {
if (a[aStart] === b[bStart]) {
aStart++;
bStart++;
continue;
}
while (a[aEnd - 1] === b[bEnd - 1]) {
aEnd--;
bEnd--;
}
if (aEnd === aStart) {
const node = bEnd < bLength ? bStart ? b[bStart - 1].nextSibling : b[bEnd - bStart] : after;
while (bStart < bEnd) parentNode.insertBefore(b[bStart++], node);
} else if (bEnd === bStart) {
while (aStart < aEnd) {
if (!map || !map.has(a[aStart])) a[aStart].remove();
aStart++;
}
} else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
const node = a[--aEnd].nextSibling;
parentNode.insertBefore(b[bStart++], a[aStart++].nextSibling);
parentNode.insertBefore(b[--bEnd], node);
a[aEnd] = b[bEnd];
} else {
if (!map) {
map = new Map();
let i = bStart;
while (i < bEnd) map.set(b[i], i++);
}
const index = map.get(a[aStart]);
if (index != null) {
if (bStart < index && index < bEnd) {
let i = aStart,
sequence = 1,
t;
while (++i < aEnd && i < bEnd) {
if ((t = map.get(a[i])) == null || t !== index + sequence) break;
sequence++;
}
if (sequence > index - bStart) {
const node = a[aStart];
while (bStart < index) parentNode.insertBefore(b[bStart++], node);
} else parentNode.replaceChild(b[bStart++], a[aStart++]);
} else aStart++;
} else a[aStart++].remove();
}
}
}
const $$EVENTS = "_$DX_DELEGATE";
function render(code, element, init, options = {}) {
let disposer;
createRoot(dispose => {
disposer = dispose;
element === document ? code() : insert(element, code(), element.firstChild ? null : undefined, init);
}, options.owner);
return () => {
disposer();
element.textContent = "";
};
}
function template(html, isImportNode, isSVG, isMathML) {
let node;
const create = () => {
const t = document.createElement("template");
t.innerHTML = html;
return t.content.firstChild;
};
const fn = () => (node || (node = create())).cloneNode(true);
fn.cloneNode = fn;
return fn;
}
function delegateEvents(eventNames, document = window.document) {
const e = document[$$EVENTS] || (document[$$EVENTS] = new Set());
for (let i = 0, l = eventNames.length; i < l; i++) {
const name = eventNames[i];
if (!e.has(name)) {
e.add(name);
document.addEventListener(name, eventHandler);
}
}
}
function setAttribute(node, name, value) {
if (value == null) node.removeAttribute(name);else node.setAttribute(name, value);
}
function setAttributeNS(node, namespace, name, value) {
if (value == null) node.removeAttributeNS(namespace, name);else node.setAttributeNS(namespace, name, value);
}
function setBoolAttribute(node, name, value) {
value ? node.setAttribute(name, "") : node.removeAttribute(name);
}
function className(node, value) {
if (value == null) node.removeAttribute("class");else node.className = value;
}
function addEventListener(node, name, handler, delegate) {
if (delegate) {
if (Array.isArray(handler)) {
node[`$$${name}`] = handler[0];
node[`$$${name}Data`] = handler[1];
} else node[`$$${name}`] = handler;
} else if (Array.isArray(handler)) {
const handlerFn = handler[0];
node.addEventListener(name, handler[0] = e => handlerFn.call(node, handler[1], e));
} else node.addEventListener(name, handler, typeof handler !== "function" && handler);
}
function classList(node, value, prev = {}) {
const classKeys = Object.keys(value || {}),
prevKeys = Object.keys(prev);
let i, len;
for (i = 0, len = prevKeys.length; i < len; i++) {
const key = prevKeys[i];
if (!key || key === "undefined" || value[key]) continue;
toggleClassKey(node, key, false);
delete prev[key];
}
for (i = 0, len = classKeys.length; i < len; i++) {
const key = classKeys[i],
classValue = !!value[key];
if (!key || key === "undefined" || prev[key] === classValue || !classValue) continue;
toggleClassKey(node, key, true);
prev[key] = classValue;
}
return prev;
}
function style(node, value, prev) {
if (!value) return prev ? setAttribute(node, "style") : value;
const nodeStyle = node.style;
if (typeof value === "string") return nodeStyle.cssText = value;
typeof prev === "string" && (nodeStyle.cssText = prev = undefined);
prev || (prev = {});
value || (value = {});
let v, s;
for (s in prev) {
value[s] == null && nodeStyle.removeProperty(s);
delete prev[s];
}
for (s in value) {
v = value[s];
if (v !== prev[s]) {
nodeStyle.setProperty(s, v);
prev[s] = v;
}
}
return prev;
}
function setStyleProperty(node, name, value) {
value != null ? node.style.setProperty(name, value) : node.style.removeProperty(name);
}
function spread(node, props = {}, isSVG, skipChildren) {
const prevProps = {};
if (!skipChildren) {
createRenderEffect(() => prevProps.children = insertExpression(node, props.children, prevProps.children));
}
createRenderEffect(() => typeof props.ref === "function" && use(props.ref, node));
createRenderEffect(() => assign(node, props, isSVG, true, prevProps, true));
return prevProps;
}
function use(fn, element, arg) {
return untrack(() => fn(element, arg));
}
function insert(parent, accessor, marker, initial) {
if (marker !== undefined && !initial) initial = [];
if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
createRenderEffect(current => insertExpression(parent, accessor(), current, marker), initial);
}
function assign(node, props, isSVG, skipChildren, prevProps = {}, skipRef = false) {
props || (props = {});
for (const prop in prevProps) {
if (!(prop in props)) {
if (prop === "children") continue;
prevProps[prop] = assignProp(node, prop, null, prevProps[prop], isSVG, skipRef, props);
}
}
for (const prop in props) {
if (prop === "children") {
continue;
}
const value = props[prop];
prevProps[prop] = assignProp(node, prop, value, prevProps[prop], isSVG, skipRef, props);
}
}
function toPropertyName(name) {
return name.toLowerCase().replace(/-([a-z])/g, (_, w) => w.toUpperCase());
}
function toggleClassKey(node, key, value) {
const classNames = key.trim().split(/\s+/);
for (let i = 0, nameLen = classNames.length; i < nameLen; i++) node.classList.toggle(classNames[i], value);
}
function assignProp(node, prop, value, prev, isSVG, skipRef, props) {
let isCE, isProp, isChildProp, propAlias, forceProp;
if (prop === "style") return style(node, value, prev);
if (prop === "classList") return classList(node, value, prev);
if (value === prev) return prev;
if (prop === "ref") {
if (!skipRef) value(node);
} else if (prop.slice(0, 3) === "on:") {
const e = prop.slice(3);
prev && node.removeEventListener(e, prev, typeof prev !== "function" && prev);
value && node.addEventListener(e, value, typeof value !== "function" && value);
} else if (prop.slice(0, 10) === "oncapture:") {
const e = prop.slice(10);
prev && node.removeEventListener(e, prev, true);
value && node.addEventListener(e, value, true);
} else if (prop.slice(0, 2) === "on") {
const name = prop.slice(2).toLowerCase();
const delegate = DelegatedEvents.has(name);
if (!delegate && prev) {
const h = Array.isArray(prev) ? prev[0] : prev;
node.removeEventListener(name, h);
}
if (delegate || value) {
addEventListener(node, name, value, delegate);
delegate && delegateEvents([name]);
}
} else if (prop.slice(0, 5) === "attr:") {
setAttribute(node, prop.slice(5), value);
} else if (prop.slice(0, 5) === "bool:") {
setBoolAttribute(node, prop.slice(5), value);
} else if ((forceProp = prop.slice(0, 5) === "prop:") || (isChildProp = ChildProperties.has(prop)) || !isSVG && ((propAlias = getPropAlias(prop, node.tagName)) || (isProp = Properties.has(prop))) || (isCE = node.nodeName.includes("-") || "is" in props)) {
if (forceProp) {
prop = prop.slice(5);
isProp = true;
}
if (prop === "class" || prop === "className") className(node, value);else if (isCE && !isProp && !isChildProp) node[toPropertyName(prop)] = value;else node[propAlias || prop] = value;
} else {
const ns = isSVG && prop.indexOf(":") > -1 && SVGNamespace[prop.split(":")[0]];
if (ns) setAttributeNS(node, ns, prop, value);else setAttribute(node, Aliases[prop] || prop, value);
}
return value;
}
function eventHandler(e) {
let node = e.target;
const key = `$$${e.type}`;
const oriTarget = e.target;
const oriCurrentTarget = e.currentTarget;
const retarget = value => Object.defineProperty(e, "target", {
configurable: true,
value
});
const handleNode = () => {
const handler = node[key];
if (handler && !node.disabled) {
const data = node[`${key}Data`];
data !== undefined ? handler.call(node, data, e) : handler.call(node, e);
if (e.cancelBubble) return;
}
node.host && typeof node.host !== "string" && !node.host._$host && node.contains(e.target) && retarget(node.host);
return true;
};
const walkUpTree = () => {
while (handleNode() && (node = node._$host || node.parentNode || node.host));
};
Object.defineProperty(e, "currentTarget", {
configurable: true,
get() {
return node || document;
}
});
if (e.composedPath) {
const path = e.composedPath();
retarget(path[0]);
for (let i = 0; i < path.length - 2; i++) {
node = path[i];
if (!handleNode()) break;
if (node._$host) {
node = node._$host;
walkUpTree();
break;
}
if (node.parentNode === oriCurrentTarget) {
break;
}
}
}
else walkUpTree();
retarget(oriTarget);
}
function insertExpression(parent, value, current, marker, unwrapArray) {
while (typeof current === "function") current = current();
if (value === current) return current;
const t = typeof value,
multi = marker !== undefined;
parent = multi && current[0] && current[0].parentNode || parent;
if (t === "string" || t === "number") {
if (t === "number") {
value = value.toString();
if (value === current) return current;
}
if (multi) {
let node = current[0];
if (node && node.nodeType === 3) {
node.data !== value && (node.data = value);
} else node = document.createTextNode(value);
current = cleanChildren(parent, current, marker, node);
} else {
if (current !== "" && typeof current === "string") {
current = parent.firstChild.data = value;
} else current = parent.textContent = value;
}
} else if (value == null || t === "boolean") {
current = cleanChildren(parent, current, marker);
} else if (t === "function") {
createRenderEffect(() => {
let v = value();
while (typeof v === "function") v = v();
current = insertExpression(parent, v, current, marker);
});
return () => current;
} else if (Array.isArray(value)) {
const array = [];
const currentArray = current && Array.isArray(current);
if (normalizeIncomingArray(array, value, current, unwrapArray)) {
createRenderEffect(() => current = insertExpression(parent, array, current, marker, true));
return () => current;
}
if (array.length === 0) {
current = cleanChildren(parent, current, marker);
if (multi) return current;
} else if (currentArray) {
if (current.length === 0) {
appendNodes(parent, array, marker);
} else reconcileArrays(parent, current, array);
} else {
current && cleanChildren(parent);
appendNodes(parent, array);
}
current = array;
} else if (value.nodeType) {
if (Array.isArray(current)) {
if (multi) return current = cleanChildren(parent, current, marker, value);
cleanChildren(parent, current, null, value);
} else if (current == null || current === "" || !parent.firstChild) {
parent.appendChild(value);
} else parent.replaceChild(value, parent.firstChild);
current = value;
} else ;
return current;
}
function normalizeIncomingArray(normalized, array, current, unwrap) {
let dynamic = false;
for (let i = 0, len = array.length; i < len; i++) {
let item = array[i],
prev = current && current[normalized.length],
t;
if (item == null || item === true || item === false) ; else if ((t = typeof item) === "object" && item.nodeType) {
normalized.push(item);
} else if (Array.isArray(item)) {
dynamic = normalizeIncomingArray(normalized, item, prev) || dynamic;
} else if (t === "function") {
if (unwrap) {
while (typeof item === "function") item = item();
dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item], Array.isArray(prev) ? prev : [prev]) || dynamic;
} else {
normalized.push(item);
dynamic = true;
}
} else {
const value = String(item);
if (prev && prev.nodeType === 3 && prev.data === value) normalized.push(prev);else normalized.push(document.createTextNode(value));
}
}
return dynamic;
}
function appendNodes(parent, array, marker = null) {
for (let i = 0, len = array.length; i < len; i++) parent.insertBefore(array[i], marker);
}
function cleanChildren(parent, current, marker, replacement) {
if (marker === undefined) return parent.textContent = "";
const node = replacement || document.createTextNode("");
if (current.length) {
let inserted = false;
for (let i = current.length - 1; i >= 0; i--) {
const el = current[i];
if (node !== el) {
const isParent = el.parentNode === parent;
if (!inserted && !i) isParent ? parent.replaceChild(node, el) : parent.insertBefore(node, marker);else isParent && el.remove();
} else inserted = true;
}
} else parent.insertBefore(node, marker);
return [node];
}
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
function createElement(tagName, isSVG = false, is = undefined) {
return isSVG ? document.createElementNS(SVG_NAMESPACE, tagName) : document.createElement(tagName, {
is
});
}
function Portal(props) {
const {
useShadow
} = props,
marker = document.createTextNode(""),
mount = () => props.mount || document.body,
owner = getOwner();
let content;
createEffect(() => {
content || (content = runWithOwner(owner, () => createMemo(() => props.children)));
const el = mount();
if (el instanceof HTMLHeadElement) {
const [clean, setClean] = createSignal(false);
const cleanup = () => setClean(true);
createRoot(dispose => insert(el, () => !clean() ? content() : dispose(), null));
onCleanup(cleanup);
} else {
const container = createElement(props.isSVG ? "g" : "div", props.isSVG),
renderRoot = useShadow && container.attachShadow ? container.attachShadow({
mode: "open"
}) : container;
Object.defineProperty(container, "_$host", {
get() {
return marker.parentNode;
},
configurable: true
});
insert(renderRoot, content);
el.appendChild(container);
props.ref && props.ref(container);
onCleanup(() => el.removeChild(container));
}
}, undefined, {
render: true
});
return marker;
}
function createDynamic(component, props) {
const cached = createMemo(component);
return createMemo(() => {
const component = cached();
switch (typeof component) {
case "function":
return untrack(() => component(props));
case "string":
const isSvg = SVGElements.has(component);
const el = createElement(component, isSvg, props.is);
spread(el, props, isSvg);
return el;
}
});
}
function Dynamic(props) {
const [, others] = splitProps(props, ["component"]);
return createDynamic(() => props.component, others);
}
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
const [panel, setPanel] = createSignal();
const [loading, setLoading] = createSignal(false);
const [forecast, setForecast] = createSignal(null);
const [error, setError] = createSignal(null);
function setForecastLoading() {
setLoading(true);
}
function setForecastLoadSuccess(forecast) {
setLoading(false);
setForecast(forecast);
setError(null);
}
function setForecastLoadFailure(error) {
setLoading(false);
setError(error);
}
const zeroOne = (value, min, max) => (value - min) / (max - min);
function GM_fetch(details) {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest(_extends({}, details, {
onload: response => resolve(response),
onerror: err => reject(err)
}));
});
}
const waitForCoordinatesToBeSetAtLeastOnce = IRF.vdom.container.then(containerVDOM => {
return new Promise(resolve => {
containerVDOM.state.changeStop = new Proxy(containerVDOM.state.changeStop, {
apply(ogChangeStop, thisArg, args) {
const returnedValue = ogChangeStop.apply(thisArg, args);
resolve();
return returnedValue;
}
});
});
});
const offsetTimezone = (date, utcOffsetSeconds) => {
const localUtcOffsetUtcSeconds = -date.getTimezoneOffset() * 60;
const result = new Date(date);
result.setSeconds(result.getSeconds() - localUtcOffsetUtcSeconds + utcOffsetSeconds);
return result;
};
const MOD_NAME = 'Thermometer';
const MOD_DOM_SAFE_PREFIX = 'thermometer-';
const MOD_LOG_PREFIX = `[${MOD_NAME}]`;
const RAD_TO_DEG = 180 / Math.PI;
const TEMPERATURE_UNITS = {
celsius: {
label: 'Celsius',
unit: '°C',
fromCelsius: celsius => celsius,
toCelsius: celsius => celsius
},
fahrenheit: {
label: 'Fahrenheit',
unit: '°F',
fromCelsius: celsius => celsius * 1.8 + 32,
toCelsius: fahrenheit => (fahrenheit - 32) / 1.8
},
felsius: {
label: 'Felsius (xkcd #1923)',
unit: '°Є',
fromCelsius: celsius => 7 * celsius / 5 + 16,
toCelsius: felsius => 5 * (felsius - 16) / 7
},
kelvin: {
label: 'Kelvin',
unit: 'K',
fromCelsius: celsius => celsius + 273,
toCelsius: kelvin => kelvin - 273
}
};
async function fetchForecast([latitude, longitude]) {
const forecastApiUrl = `https://api.open-meteo.com/v1/forecast?${[`latitude=${latitude}`, `longitude=${longitude}`, `current=${['temperature_2m', 'relative_humidity_2m'].join(',')}`, `temperature_unit=celsius`, `timezone=auto`].join('&')}`;
const {
status,
response: forecastStr
} = await GM_fetch({
url: forecastApiUrl,
headers: {
'content-type': 'application/json'
},
timeout: 10000
});
if (status !== 200) {
throw new Error(`Got a ${status} status code when requesting forecast information`);
}
if (forecastStr == null) {
throw new Error(`For some reason the forecast information was nullish`);
}
let forecast;
try {
forecast = JSON.parse(forecastStr);
} catch (error) {
throw new Error(`Could not parse forecast JSON: ${error.toString()}`);
}
return forecast;
}
/* #region Widget Position */
const DEFAULT_WIDGET_POSITION = {
x: 0.5,
y: 0.5
};
/* #endregion Overlay Position */
/* #region Default Temperature Gradient */
const DEFAULT_TEMPERATURE_GRADIENT = [{
temperatureCelsius: -20,
color: '#f5f5f5'
}, {
temperatureCelsius: -10,
color: '#82cdff'
}, {
temperatureCelsius: 0,
color: '#0c9eff'
}, {
temperatureCelsius: 15,
color: '#043add'
}, {
temperatureCelsius: 18.5,
color: '#c0c23d'
}, {
temperatureCelsius: 22.5,
color: '#ffd86d'
}, {
temperatureCelsius: 27.5,
color: '#ffa538'
}, {
temperatureCelsius: 32.5,
color: '#c92626'
}, {
temperatureCelsius: 40,
color: '#6a0b39'
}];
const {
min: DEFAULT_TEMPERATURE_GRADIENT_MIN_TEMPERATURE,
max: DEFAULT_TEMPERATURE_GRADIENT_MAX_TEMPERATURE
} = DEFAULT_TEMPERATURE_GRADIENT.reduce(({
min: previousMin,
max: previousMax
}, {
temperatureCelsius
}) => ({
min: Math.min(temperatureCelsius, previousMin),
max: Math.max(temperatureCelsius, previousMax)
}), {
min: 0,
max: 0
});
const getDefaultTemperatureGradientSettings = () => ({
temperatureGradient: DEFAULT_TEMPERATURE_GRADIENT.map(({
temperatureCelsius,
color
}) => ({
percent: zeroOne(temperatureCelsius, DEFAULT_TEMPERATURE_GRADIENT_MIN_TEMPERATURE, DEFAULT_TEMPERATURE_GRADIENT_MAX_TEMPERATURE),
color
})),
temperatureGradientMinCelsius: DEFAULT_TEMPERATURE_GRADIENT_MIN_TEMPERATURE,
temperatureGradientMaxCelsius: DEFAULT_TEMPERATURE_GRADIENT_MAX_TEMPERATURE
});
/* #endregion Default Temperature Gradient */
const [settings, setSettings] = createSignal(_extends({
widgetPosition: DEFAULT_WIDGET_POSITION,
showClock: true,
time24Hours: true,
timeIncludeSeconds: false,
showTemperature: true,
temperatureUnit: 'celsius'
}, getDefaultTemperatureGradientSettings(), {
showRelativeHumidity: true
}));
// Load
{
const newSettings = _extends({}, settings());
for (const key in settings()) {
newSettings[key] = await( GM.getValue(key, newSettings[key]));
}
// Migration from <2.0.0, when the widget was refered to as an overlay
{
const noValue = Symbol();
const hasWidgetPositionValue = (await( GM.getValue('widgetPosition', noValue))) === noValue;
const oldOverlayPositionValue = await( GM.getValue('overlayPosition', noValue));
if (!hasWidgetPositionValue && oldOverlayPositionValue !== noValue) {
newSettings.widgetPosition = oldOverlayPositionValue;
await( GM.deleteValue('overlayPosition'));
}
}
// Migration from <=1.2.2, when the widget position was null by default
if (newSettings.widgetPosition == null) {
newSettings.widgetPosition = DEFAULT_WIDGET_POSITION;
}
setSettings(newSettings);
}
// Save
async function saveSettings(change) {
if (typeof change === 'function') {
setSettings(change);
} else if (typeof change === 'object') {
setSettings(prevSettings => _extends({}, prevSettings, change));
}
const currentSettings = settings();
for (const key in currentSettings) {
await GM.setValue(key, currentSettings[key]);
}
}
/*
* This whole file is heavily based on https://github.com/violentmonkey/vm-ui/blob/00592622a01e48a4ac27a743254d82b1ebcd6d02/src/util/movable.ts
* with the following modifications:
* - Make Movable an EventTarget
* - Add move-start, moving, and move-end events to Movable
* - Replaces the old onMoved callback in MovableOptions
* - Add handler elements system to Movable
* - Add methods to retrieve and set position of the Movable
* - Add support for touch events
*/
class Movable extends EventTarget {
constructor(el, options) {
super();
this.el = null;
this.options = null;
this.dragging = null;
this.isTouchEvent = e => e.type.startsWith('touch');
this.getEventPointerPosition = e => {
if (this.isTouchEvent(e)) {
const {
clientX,
clientY
} = e.touches[this.touchIdentifier];
return {
clientX,
clientY
};
} else {
const {
clientX,
clientY
} = e;
return {
clientX,
clientY
};
}
};
this.onMouseDown = e => {
if (this.isTouchEvent(e)) {
var _e$changedTouches;
this.touchIdentifier = (_e$changedTouches = e.changedTouches) == null || (_e$changedTouches = _e$changedTouches[0]) == null ? void 0 : _e$changedTouches.identifier;
}
const {
handlerElements = []
} = this.options;
if (handlerElements.length > 0 && !handlerElements.some(handlerEl => e.target === handlerEl || handlerEl.contains(e.target))) {
return;
}
e.preventDefault();
e.stopPropagation();
const {
x,
y
} = this.el.getBoundingClientRect();
const {
clientX,
clientY
} = this.getEventPointerPosition(e);
this.dragging = {
x: clientX - x,
y: clientY - y
};
document.addEventListener('mousemove', this.onMouseMove);
document.addEventListener('touchmove', this.onMouseMove);
document.addEventListener('mouseup', this.onMouseUp);
document.addEventListener('touchend', this.onMouseUp);
this.dispatchEvent(new Event('move-start'));
};
this.onMouseMove = e => {
if (this.isTouchEvent(e) && this.touchIdentifier != null && !Array.from(e.changedTouches).some(touch => this.touchIdentifier === touch.identifier)) return;
if (!this.dragging) return;
const {
x,
y
} = this.dragging;
const {
clientX,
clientY
} = this.getEventPointerPosition(e);
this.setPosition(clientX - x, clientY - y);
};
this.onMouseUp = e => {
if (this.isTouchEvent(e) && this.touchIdentifier != null && !Array.from(e.changedTouches).some(touch => this.touchIdentifier === touch.identifier)) return;
this.dragging = null;
this.touchIdentifier = null;
document.removeEventListener('mousemove', this.onMouseMove);
document.removeEventListener('touchmove', this.onMouseMove);
document.removeEventListener('mouseup', this.onMouseUp);
document.removeEventListener('touchend', this.onMouseUp);
this.dispatchEvent(new Event('move-end'));
};
this.el = el;
this.setOptions(options);
}
setOptions(options) {
this.options = _extends({}, Movable.defaultOptions, options);
}
applyOptions(newOptions) {
this.options = _extends({}, this.options, newOptions);
}
enable() {
this.el.addEventListener('mousedown', this.onMouseDown);
this.el.addEventListener('touchstart', this.onMouseDown);
}
disable() {
this.dragging = undefined;
this.el.removeEventListener('mousedown', this.onMouseDown);
this.el.removeEventListener('touchstart', this.onMouseDown);
document.removeEventListener('mousemove', this.onMouseMove);
document.removeEventListener('touchmove', this.onMouseUp);
document.removeEventListener('mouseup', this.onMouseUp);
document.removeEventListener('touchend', this.onMouseUp);
}
setPosition(x, y) {
const {
origin
} = this.options;
const {
offsetWidth: width,
offsetHeight: height
} = this.el;
const {
clientWidth,
clientHeight
} = document.documentElement;
const left = Math.max(0, Math.min(x, clientWidth - width));
const top = Math.max(0, Math.min(y, clientHeight - height));
const position = {
top: 'auto',
left: 'auto',
right: 'auto',
bottom: 'auto'
};
if (origin.x === 'start' || origin.x === 'auto' && left + left + width < clientWidth) {
position.left = `${left}px`;
} else {
position.right = `${clientWidth - left - width}px`;
}
if (origin.y === 'start' || origin.y === 'auto' && top + top + height < clientHeight) {
position.top = `${top}px`;
} else {
position.bottom = `${clientHeight - top - height}px`;
}
Object.assign(this.el.style, position);
this.dispatchEvent(new Event('moving'));
}
getPosition() {
const {
left,
top
} = this.el.getBoundingClientRect();
return {
left,
top
};
}
}
Movable.defaultOptions = {
origin: {
x: 'auto',
y: 'auto'
}
};
const shadowRootContext = createContext(null);
var ShadowRooted = props => {
props = mergeProps({
hostTag: 'div'
}, props);
let hostEl;
const [shadowRoot, setShadowRoot] = createSignal(null);
onMount(() => {
setShadowRoot(hostEl.attachShadow({
mode: 'open'
}));
});
return [createComponent(Dynamic, {
get component() {
return props.hostTag;
},
style: {
display: 'contents'
},
ref(r$) {
var _ref$ = hostEl;
typeof _ref$ === "function" ? _ref$(r$) : hostEl = r$;
}
}), createComponent(shadowRootContext.Provider, {
value: shadowRoot,
get children() {
return createComponent(Show, {
get when() {
return (shadowRoot == null ? void 0 : shadowRoot()) != null;
},
get children() {
return createComponent(Portal, {
get mount() {
return shadowRoot();
},
get children() {
return props.children;
}
});
}
});
}
})];
};
/*
* Based on VM UI's VM.getPanel() https://github.com/violentmonkey/vm-ui/blob/00592622a01e48a4ac27a743254d82b1ebcd6d02/src/panel/index.tsx#L71
*/
function makePanel(options) {
var _options$zIndex;
const hostEl = dom.hm(`${MOD_DOM_SAFE_PREFIX}host`, {});
const shadowRoot = hostEl.attachShadow({
mode: 'open'
});
shadowRoot.append(dom.hm('style', {}, `
${MOD_DOM_SAFE_PREFIX}wrapper {
position: fixed;
z-index: ${(_options$zIndex = options.zIndex) != null ? _options$zIndex : Number.MAX_SAFE_INTEGER - 1};
pointer-events: none;
& > * {
pointer-events: initial;
}
}
`));
const wrapperEl = dom.hm(`${MOD_DOM_SAFE_PREFIX}wrapper`, {});
wrapperEl.style.pointerEvents = 'none';
shadowRoot.append(wrapperEl);
const movable = new Movable(wrapperEl);
const panel = {
hostEl,
wrapperEl,
movable,
show() {
document.body.append(hostEl);
},
hide() {
hostEl.remove();
}
};
render(() => {
return createComponent(shadowRootContext.Provider, {
value: () => shadowRoot,
get children() {
return createComponent(options.element, {
panel: panel
});
}
});
}, panel.wrapperEl);
return panel;
}
var styles$6 = {"container":"thermometer-ThermometerWidget-container","graphic":"thermometer-ThermometerWidget-graphic","is-being-grabbed":"thermometer-ThermometerWidget-is-being-grabbed","info":"thermometer-ThermometerWidget-info","error":"thermometer-ThermometerWidget-error","is-info-on-the-right":"thermometer-ThermometerWidget-is-info-on-the-right"};
var stylesheet$9=".thermometer-ThermometerWidget-container {\n /* Theme: transparent */\n background-color: transparent;\n border: none;\n box-shadow: none;\n\n /* Fix for loading indicator making the container super big */\n width: fit-content;\n height: fit-content;\n\n padding: 0;\n display: flex;\n flex-direction: row;\n}\n\n .thermometer-ThermometerWidget-container > * {\n pointer-events: initial;\n }\n\n .thermometer-ThermometerWidget-container .thermometer-ThermometerWidget-graphic {\n cursor: grab;\n }\n\n .thermometer-ThermometerWidget-is-being-grabbed .thermometer-ThermometerWidget-container .thermometer-ThermometerWidget-graphic {\n cursor: grabbing;\n }\n\n .thermometer-ThermometerWidget-container .thermometer-ThermometerWidget-info {\n min-width: 8ch;\n margin-block: 0.5rem;\n font-family: 'Roboto', sans-serif;\n color: white;\n text-shadow:\n 0px 1px 2px black,\n 0px -1px 2px black,\n 1px 0px 2px black,\n -1px 0px 2px black;\n text-align: right;\n }\n\n .thermometer-ThermometerWidget-container .thermometer-ThermometerWidget-info :is(p, h1, h2, h3, h4, h5, h6) {\n margin: 0;\n }\n\n .thermometer-ThermometerWidget-container .thermometer-ThermometerWidget-error {\n color: #ff4141;\n }\n\n .thermometer-ThermometerWidget-container.thermometer-ThermometerWidget-is-info-on-the-right {\n flex-direction: row-reverse;\n }\n\n .thermometer-ThermometerWidget-container.thermometer-ThermometerWidget-is-info-on-the-right .thermometer-ThermometerWidget-info {\n text-align: left;\n }\n";
const temperatureGradientCanvasCtx = document.createElement('canvas').getContext('2d', {
willReadFrequently: true
});
const temperatureGradientCanvas = temperatureGradientCanvasCtx.canvas;
const [temperatureGradientHasRedrawn, setTemperatureGradientHasRedrawn] = createSignal(false);
const pingTemperatureGradientHasRedrawn = () => {
// Abusing Solid's signal system since 2025
setTemperatureGradientHasRedrawn(true);
setTemperatureGradientHasRedrawn(false);
};
const [temperatureGradientArray, setTemperatureGradientArray] = createSignal(null);
createEffect(() => {
if (temperatureGradientArray()) {
pingTemperatureGradientHasRedrawn();
}
});
function redrawTemperatureCanvas() {
const canvasWidth = Math.abs(settings().temperatureGradientMaxCelsius - settings().temperatureGradientMinCelsius);
temperatureGradientCanvasCtx.canvas.width = canvasWidth;
temperatureGradientCanvasCtx.canvas.height = 1;
const temperatureCanvasGradient = temperatureGradientCanvasCtx.createLinearGradient(0, 0, temperatureGradientCanvasCtx.canvas.width, 0);
for (const {
percent,
color
} of settings().temperatureGradient) {
temperatureCanvasGradient.addColorStop(percent, color);
}
temperatureGradientCanvasCtx.fillStyle = temperatureCanvasGradient;
temperatureGradientCanvasCtx.fillRect(0, 0, temperatureGradientCanvasCtx.canvas.width, temperatureGradientCanvasCtx.canvas.height);
}
createEffect(() => {
var _settings;
if (((_settings = settings()) == null ? void 0 : _settings.temperatureGradient) != null) {
redrawTemperatureCanvas();
setTemperatureGradientArray(temperatureGradientCanvasCtx.getImageData(0, 0, temperatureGradientCanvasCtx.canvas.width, 1).data);
}
});
function sampleTemperatureGradient(temperatureCelsius) {
const percent = zeroOne(temperatureCelsius, settings().temperatureGradientMinCelsius, settings().temperatureGradientMaxCelsius);
const clampedPercent = Math.max(0, Math.min(percent, 1));
const x = Math.floor(clampedPercent * (temperatureGradientCanvasCtx.canvas.width - 1));
const i = x * 4;
const rgb = temperatureGradientArray().slice(i, i + 3);
return '#' + [...rgb].map(c => c.toString(16).padStart(2, '0')).join('');
}
const temperatureAtGradient = x => settings().temperatureGradientMinCelsius + x * (settings().temperatureGradientMaxCelsius - settings().temperatureGradientMinCelsius);
const existingStyleSheetsPerRealm = new Map();
const useRealm = () => {
const closestShadowRoot = useContext(shadowRootContext);
return () => closestShadowRoot == null ? document : closestShadowRoot();
};
var SingleInstanceStyle = props => {
const mountRealm = useRealm();
const mount = () => {
const realm = mountRealm();
if (realm instanceof Document) {
return realm.head;
} else if (realm instanceof ShadowRoot) {
return realm;
} else {
throw new Error('Could not determine mount point for SingleInstanceStylesheet');
}
};
const injectIfFirst = () => {
if (!existingStyleSheetsPerRealm.has(mountRealm())) {
existingStyleSheetsPerRealm.set(mountRealm(), new Map());
}
const realmExistingStyleSheets = existingStyleSheetsPerRealm.get(mountRealm());
if (!realmExistingStyleSheets.has(props.key)) {
const [usageCount, setUsageCount] = createSignal(0);
realmExistingStyleSheets.set(props.key, {
styleEl: dom.hm('style', {
'data-single-instance-style-key': props.key
}, props.children),
usageCount,
setUsageCount
});
}
const instance = realmExistingStyleSheets.get(props.key);
instance.setUsageCount(count => count + 1);
if (!instance.styleEl.isConnected) {
mount().append(instance.styleEl);
}
};
const cleanUpForThisInstance = () => {
const realmExistingStyleSheets = existingStyleSheetsPerRealm.get(mountRealm());
if (!realmExistingStyleSheets) {
return;
}
const instance = realmExistingStyleSheets.get(props.key);
if (!instance) {
return;
}
instance.setUsageCount(count => count - 1);
if (instance.usageCount() <= 0 && instance.styleEl.isConnected) {
instance.styleEl.remove();
realmExistingStyleSheets.delete(props.key);
}
};
createEffect(() => {
if (mountRealm() == null) {
return;
}
injectIfFirst();
});
onCleanup(cleanUpForThisInstance);
return null;
};
var styles$5 = {"graphic":"thermometer-ThermometerGraphic-graphic","fill":"thermometer-ThermometerGraphic-fill","bottom-fill":"thermometer-ThermometerGraphic-bottom-fill","is-busy":"thermometer-ThermometerGraphic-is-busy"};
var stylesheet$8=".thermometer-ThermometerGraphic-graphic {\n padding: 0.5rem;\n align-self: center;\n}\n\n .thermometer-ThermometerGraphic-graphic .thermometer-ThermometerGraphic-fill,\n .thermometer-ThermometerGraphic-graphic .thermometer-ThermometerGraphic-bottom-fill {\n transition:\n height 1s linear,\n color 1s linear;\n }\n\n .thermometer-ThermometerGraphic-is-busy .thermometer-ThermometerGraphic-graphic {\n animation: 1s linear infinite alternate thermometer-ThermometerGraphic-busy;\n }\n\n .thermometer-ThermometerGraphic-is-busy .thermometer-ThermometerGraphic-graphic .thermometer-ThermometerGraphic-fill,\n .thermometer-ThermometerGraphic-is-busy .thermometer-ThermometerGraphic-graphic .thermometer-ThermometerGraphic-bottom-fill {\n transition: none;\n }\n\n@keyframes thermometer-ThermometerGraphic-busy {\n 0% {\n color: lightgray;\n }\n 100% {\n color: darkgray;\n }\n}\n";
var _tmpl$$6 = /*#__PURE__*/template(`<svg width=22 height=100 viewBox="0 0 22 100"xmlns=http://www.w3.org/2000/svg><rect id=background width=9.67380575 height=84.6300375 x=5.841754249999994 y=1.9680687500000005 rx=4.5632865016684 style="fill:rgb(206 251 250 / 50%);fill-opacity:1;opacity:1;stroke:none;stroke-dasharray:none;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;stroke-width:2"></rect><rect id=fill width=9.67380575 x=5.841754249999994 y=14 rx=4.5632865016684 style=fill:currentcolor;fill-opacity:1;opacity:1;stroke:none;stroke-dasharray:none;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;stroke-width:2;rotate:180deg;transform-origin:center></rect><g><ellipse id=bottom-fill cx=10.81210005114999 cy=89.515713524548 rx=9.596004408359999 ry=9.3861523188054 style=opacity:1;fill:currentcolor;fill-opacity:1;stroke-width:2.98623;stroke-linecap:round;stroke-linejoin:round></ellipse><path id=shimmer d="M 3.68829 88.8769 C 3.68829 88.8769 3.94517 91.2262 5.70701 93.1668 C 7.46886 95.1075 9.59383 95.4109 9.59383 95.4109"style=fill:none;opacity:0.75;stroke:#ffffff;stroke-dasharray:none;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;stroke-width:1.5></path></g><path id=outline d="M 10.6362 1.09869 C 7.89534 1.09869 5.69054 3.76255 5.69055 7.07108 L 5.69055 80.5387 C 2.8139 82.2685 0.910186 85.417 0.910186 89.0143 C 0.910186 94.4748 5.34273 98.9026 10.8115 98.9026 C 16.2803 98.9026 20.7148 94.4748 20.7148 89.0143 C 20.7148 85.2876 18.6337 82.0426 15.5838 80.3576 L 15.5838 7.07108 C 15.5838 3.76255 13.377 1.09869 10.6362 1.09869 Z"style=display:inline;fill:none;stroke:#000000;stroke-dasharray:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:2>`);
var ThermometerGraphic = props => {
const GRAPHIC_MAX_HEIGHT = 84.6300375;
const [mainProps, restProps] = splitProps(props, ['busy', 'fillPercent', 'color']);
return [createComponent(SingleInstanceStyle, {
key: "ThermometerGraphic",
children: stylesheet$8
}), (() => {
var _el$ = _tmpl$$6(),
_el$2 = _el$.firstChild,
_el$3 = _el$2.nextSibling,
_el$4 = _el$3.nextSibling,
_el$5 = _el$4.firstChild;
spread(_el$, mergeProps({
get style() {
return {
color: !mainProps.busy ? mainProps.color : null
};
}
}, restProps, {
get classList() {
var _restProps$class$spli, _restProps$class;
return _extends({}, Object.fromEntries((_restProps$class$spli = (_restProps$class = restProps['class']) == null ? void 0 : _restProps$class.split(' ').map(cls => [cls, true])) != null ? _restProps$class$spli : []), {
[styles$5['graphic']]: true,
[styles$5['is-busy']]: mainProps.busy
});
}
}), true, true);
createRenderEffect(_p$ => {
var _v$ = styles$5['fill'],
_v$2 = mainProps.fillPercent * GRAPHIC_MAX_HEIGHT,
_v$3 = styles$5['bottom-fill'];
_v$ !== _p$.e && setAttribute(_el$3, "class", _p$.e = _v$);
_v$2 !== _p$.t && setAttribute(_el$3, "height", _p$.t = _v$2);
_v$3 !== _p$.a && setAttribute(_el$5, "class", _p$.a = _v$3);
return _p$;
}, {
e: undefined,
t: undefined,
a: undefined
});
return _el$;
})()];
};
function _classPrivateFieldBase(e, t) {
if (!{}.hasOwnProperty.call(e, t)) throw new TypeError("attempted to use private field on non-instance");
return e;
}
var id = 0;
function _classPrivateFieldKey(e) {
return "__private_" + id++ + "_" + e;
}
var _interval = /*#__PURE__*/_classPrivateFieldKey("interval");
var _mousePositionInsidePet = /*#__PURE__*/_classPrivateFieldKey("mousePositionInsidePet");
var _lastMousePositionInsidePet = /*#__PURE__*/_classPrivateFieldKey("lastMousePositionInsidePet");
var _lastAngle = /*#__PURE__*/_classPrivateFieldKey("lastAngle");
var _scratchCounter = /*#__PURE__*/_classPrivateFieldKey("scratchCounter");
var _samplesBeingPet = /*#__PURE__*/_classPrivateFieldKey("samplesBeingPet");
var _lastEventDispatchWasPettingStart = /*#__PURE__*/_classPrivateFieldKey("lastEventDispatchWasPettingStart");
var _handlePetElMouseMove = /*#__PURE__*/_classPrivateFieldKey("handlePetElMouseMove");
var _handlePetElMouseLeave = /*#__PURE__*/_classPrivateFieldKey("handlePetElMouseLeave");
var _sample = /*#__PURE__*/_classPrivateFieldKey("sample");
class Pet extends EventTarget {
constructor(petEl, options) {
super();
Object.defineProperty(this, _sample, {
value: _sample2
});
Object.defineProperty(this, _interval, {
writable: true,
value: null
});
Object.defineProperty(this, _mousePositionInsidePet, {
writable: true,
value: null
});
Object.defineProperty(this, _lastMousePositionInsidePet, {
writable: true,
value: null
});
Object.defineProperty(this, _lastAngle, {
writable: true,
value: null
});
Object.defineProperty(this, _scratchCounter, {
writable: true,
value: 0
});
Object.defineProperty(this, _samplesBeingPet, {
writable: true,
value: 0
});
Object.defineProperty(this, _lastEventDispatchWasPettingStart, {
writable: true,
value: false
});
Object.defineProperty(this, _handlePetElMouseMove, {
writable: true,
value: event => {
const {
clientX,
clientY
} = event;
_classPrivateFieldBase(this, _mousePositionInsidePet)[_mousePositionInsidePet] = {
x: clientX,
y: clientY
};
}
});
Object.defineProperty(this, _handlePetElMouseLeave, {
writable: true,
value: _event => {
_classPrivateFieldBase(this, _mousePositionInsidePet)[_mousePositionInsidePet] = null;
}
});
this.petEl = petEl;
this.options = options;
this.start();
}
start() {
_classPrivateFieldBase(this, _interval)[_interval] = setInterval(_classPrivateFieldBase(this, _sample)[_sample].bind(this), this.options.sampleRate);
_classPrivateFieldBase(this, _mousePositionInsidePet)[_mousePositionInsidePet] = null;
this.petEl.addEventListener('mousemove', _classPrivateFieldBase(this, _handlePetElMouseMove)[_handlePetElMouseMove].bind(this));
this.petEl.addEventListener('mouseleave', _classPrivateFieldBase(this, _handlePetElMouseLeave)[_handlePetElMouseLeave].bind(this));
}
stop() {
clearInterval(_classPrivateFieldBase(this, _interval)[_interval]);
_classPrivateFieldBase(this, _interval)[_interval] = null;
this.petEl.removeEventListener('mousemove', _classPrivateFieldBase(this, _handlePetElMouseMove)[_handlePetElMouseMove]);
this.petEl.removeEventListener('mouseleave', _classPrivateFieldBase(this, _handlePetElMouseLeave)[_handlePetElMouseLeave]);
}
}
function _sample2() {
if (_classPrivateFieldBase(this, _lastMousePositionInsidePet)[_lastMousePositionInsidePet] != null && _classPrivateFieldBase(this, _mousePositionInsidePet)[_mousePositionInsidePet] != null) {
const normalizedX = _classPrivateFieldBase(this, _mousePositionInsidePet)[_mousePositionInsidePet].x - _classPrivateFieldBase(this, _lastMousePositionInsidePet)[_lastMousePositionInsidePet].x;
const normalizedY = _classPrivateFieldBase(this, _mousePositionInsidePet)[_mousePositionInsidePet].y - _classPrivateFieldBase(this, _lastMousePositionInsidePet)[_lastMousePositionInsidePet].y;
const angle = Math.atan2(normalizedX, normalizedY) * RAD_TO_DEG;
if (_classPrivateFieldBase(this, _lastAngle)[_lastAngle] != null) {
const anglesDistance = (_classPrivateFieldBase(this, _lastAngle)[_lastAngle] - angle + 180) % 360 - 180;
const absAnglesDistance = Math.abs(anglesDistance);
// console.debug(MOD_LOG_PREFIX, 'absAnglesDistance:', absAnglesDistance);
if (absAnglesDistance > this.options.angleMin + 180 && absAnglesDistance < this.options.angleMax + 180) {
_classPrivateFieldBase(this, _scratchCounter)[_scratchCounter] = Math.min(_classPrivateFieldBase(this, _scratchCounter)[_scratchCounter] + 1, this.options.max);
}
const isBeingPet = _classPrivateFieldBase(this, _scratchCounter)[_scratchCounter] > this.options.threshold;
if (isBeingPet) {
_classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] = Math.min(_classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] + 1, this.options.activation);
} else {
_classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] = Math.max(0, _classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] - 1);
}
// console.debug(MOD_LOG_PREFIX, 'isBeingPet:', isBeingPet);
// console.debug(MOD_LOG_PREFIX, 'samplesBeingPet:', this.#samplesBeingPet);
if (isBeingPet && _classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] >= this.options.activation) {
if (!_classPrivateFieldBase(this, _lastEventDispatchWasPettingStart)[_lastEventDispatchWasPettingStart]) {
this.dispatchEvent(new Event('petting-start'));
_classPrivateFieldBase(this, _lastEventDispatchWasPettingStart)[_lastEventDispatchWasPettingStart] = true;
}
} else {
if (_classPrivateFieldBase(this, _lastEventDispatchWasPettingStart)[_lastEventDispatchWasPettingStart]) {
this.dispatchEvent(new Event('petting-end'));
_classPrivateFieldBase(this, _lastEventDispatchWasPettingStart)[_lastEventDispatchWasPettingStart] = false;
}
}
}
_classPrivateFieldBase(this, _lastAngle)[_lastAngle] = angle;
} else {
_classPrivateFieldBase(this, _lastAngle)[_lastAngle] = null;
_classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] = Math.max(0, _classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] - 0.75);
if (_classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] === 0 && _classPrivateFieldBase(this, _lastEventDispatchWasPettingStart)[_lastEventDispatchWasPettingStart]) {
this.dispatchEvent(new Event('petting-end'));
_classPrivateFieldBase(this, _lastEventDispatchWasPettingStart)[_lastEventDispatchWasPettingStart] = false;
}
}
_classPrivateFieldBase(this, _lastMousePositionInsidePet)[_lastMousePositionInsidePet] = _classPrivateFieldBase(this, _mousePositionInsidePet)[_mousePositionInsidePet];
_classPrivateFieldBase(this, _scratchCounter)[_scratchCounter] = Math.max(0, _classPrivateFieldBase(this, _scratchCounter)[_scratchCounter] - this.options.neglect);
}
var styles$4 = {"petting-heart":"thermometer-heart-petting-heart"};
var stylesheet$7="@keyframes thermometer-heart-petting-heart-movement {\n 0% {\n translate: 0 0;\n rotate: 0deg;\n }\n 25% {\n translate: -10% 0;\n rotate: 25deg;\n }\n 50% {\n translate: 0% 0;\n rotate: 0deg;\n }\n 75% {\n translate: 10% 0;\n rotate: -25deg;\n }\n}\n\n/* Based on https://css-tricks.com/hearts-in-html-and-css/#aa-css-shape */\n.thermometer-heart-petting-heart {\n --size: 10px;\n\n position: fixed;\n background-color: red;\n margin: 0 calc(var(--size)/3);\n width: var(--size);\n aspect-ratio: 1;\n display: inline-block;\n transform: translate(-50%, -50%) rotate(-45deg);\n animation: thermometer-heart-petting-heart-movement 2s linear infinite;\n z-index: 0;\n}\n.thermometer-heart-petting-heart::before,\n .thermometer-heart-petting-heart::after {\n content: '';\n position: absolute;\n width: var(--size);\n aspect-ratio: 1;\n border-radius: 50%;\n background-color: red;\n }\n.thermometer-heart-petting-heart::before {\n top: calc(var(--size)/-2);\n left: 0;\n }\n.thermometer-heart-petting-heart::after {\n left: calc(var(--size)/2);\n top: 0;\n }\n";
class Heart {
constructor({
initialPosX,
initialPosY,
velocityX,
velocityY,
maxLifetime
}) {
this.el = document.createElement('div');
this.el.classList.add(styles$4['petting-heart']);
document.body.append(this.el);
this.maxLifetime = maxLifetime;
this.lifetime = this.maxLifetime;
this.x = initialPosX;
this.y = initialPosY;
this.velocityX = velocityX;
this.velocityY = velocityY;
this.lastUpdateTimestamp = Date.now();
this.update();
}
update() {
this.lifetime -= Date.now() - this.lastUpdateTimestamp;
if (this.lifetime <= 0) {
this.el.remove();
return;
}
this.x += this.velocityX;
this.y += this.velocityY;
this.el.style.left = `${this.x}px`;
this.el.style.top = `${this.y}px`;
this.el.style.scale = (this.lifetime / this.maxLifetime).toString(10);
this.el.style.opacity = (1.5 * this.lifetime / this.maxLifetime).toString(10);
this.lastUpdateTimestamp = Date.now();
requestAnimationFrame(this.update.bind(this));
}
}
GM_addStyle(stylesheet$7);
var _tmpl$$5 = /*#__PURE__*/template(`<div><h2>:(</h2><p>Contact @netux about this`),
_tmpl$2$2 = /*#__PURE__*/template(`<p>`),
_tmpl$3$2 = /*#__PURE__*/template(`<p>T: `),
_tmpl$4$2 = /*#__PURE__*/template(`<p>H: <!>%`),
_tmpl$5$1 = /*#__PURE__*/template(`<div><div>`);
const CLOCK_EMOJIS_MAP = new Map();
{
const firstOClockEmojiCodePoint = '🕐'.codePointAt(0); // one-oclock
const lastOClockEmojiCodePoint = '🕛'.codePointAt(0); // twelve-oclock
for (let i = 0; i <= lastOClockEmojiCodePoint - firstOClockEmojiCodePoint; i++) {
CLOCK_EMOJIS_MAP.set((i + 1) % 12, String.fromCodePoint(firstOClockEmojiCodePoint + i));
}
const firstThirtyEmojiCodePoint = '🕜'.codePointAt(0);
const lastThirtyEmojiCodePoint = '🕧'.codePointAt(0);
for (let i = 0; i <= lastThirtyEmojiCodePoint - firstThirtyEmojiCodePoint; i++) {
CLOCK_EMOJIS_MAP.set((i + 1.5) % 12, String.fromCodePoint(firstThirtyEmojiCodePoint + i));
}
}
const useCurrentDate = updateEveryMs => {
const [date, setDate] = createSignal(new Date());
const updateInterval = setInterval(() => setDate(new Date()), updateEveryMs);
onCleanup(() => {
clearInterval(updateInterval);
});
return date;
};
const ForecastZoneDateClock = props => {
const currentDate = useCurrentDate(1000);
const forecastZoneDate = createMemo(() => offsetTimezone(currentDate(), props.forecast.utc_offset_seconds));
const forecastZoneDateEmoji = () => {
const decimalHour = forecastZoneDate().getHours() % 12 + (forecastZoneDate().getMinutes() >= 30 ? 0.5 : 0);
return CLOCK_EMOJIS_MAP.get(decimalHour) || '⏰';
};
const prettyForecastZoneDate = createMemo(() => {
const date = forecastZoneDate();
const pad = n => n.toString().padStart(2, '0');
if (settings().time24Hours) {
return `${pad(date.getHours())}:${pad(date.getMinutes())}${settings().timeIncludeSeconds ? `:${pad(date.getSeconds())}` : ''}`;
} else {
return `${pad(date.getHours() % 12)}:${pad(date.getMinutes())}${settings().timeIncludeSeconds ? `:${pad(date.getSeconds())}` : ''} ${date.getHours() >= 12 ? 'PM' : 'AM'}`;
}
});
return [memo(forecastZoneDateEmoji), " ", memo(prettyForecastZoneDate)];
};
var Thermometer = ({
panel
}) => {
let graphicEl;
const [isBeingGrabbed, setIsBeingGrabbed] = createSignal(false);
const [isInfoOnTheRight, setIsInfoOnTheRight] = createSignal(false);
const onPanelMoving = () => {
const {
left,
width
} = panel.wrapperEl.getBoundingClientRect();
setIsInfoOnTheRight(left + width / 2 < window.innerWidth / 2);
};
const onPanelMoveStart = () => {
setIsBeingGrabbed(true);
};
const onPanelMoveEnd = async () => {
setIsBeingGrabbed(false);
const {
left,
top
} = panel.movable.getPosition();
await saveSettings(prevSettings => {
return _extends({}, prevSettings, {
widgetPosition: {
x: left / window.innerWidth,
y: top / window.innerHeight
}
});
});
};
const setupPet = () => {
const pet = new Pet(graphicEl, {
sampleRate: 100,
max: 10,
threshold: 2.5,
activation: 3,
neglect: 0.5,
angleMin: -20,
angleMax: 20
});
let spawnHeartsInterval = null;
pet.addEventListener('petting-start', () => {
let spawnNextHeartGoingRight = false;
spawnHeartsInterval = setInterval(() => {
const boundingBox = pet.petEl.getBoundingClientRect();
new Heart({
initialPosX: boundingBox.left + boundingBox.width * (spawnNextHeartGoingRight ? 2 / 3 : 1 / 3),
initialPosY: boundingBox.top + boundingBox.height / 3,
velocityX: 0.25 * (spawnNextHeartGoingRight ? 1 : -1),
velocityY: -0.5,
maxLifetime: 5000
});
spawnNextHeartGoingRight = !spawnNextHeartGoingRight;
}, 500);
});
pet.addEventListener('petting-end', () => {
clearInterval(spawnHeartsInterval);
spawnHeartsInterval = null;
});
};
onMount(() => {
panel.movable.addEventListener('move-start', onPanelMoveStart);
panel.movable.addEventListener('moving', onPanelMoving);
panel.movable.addEventListener('move-end', onPanelMoveEnd);
panel.movable.applyOptions({
handlerElements: [graphicEl]
});
panel.movable.enable();
setupPet();
});
onCleanup(() => {
panel.movable.disable();
panel.movable.removeEventListener('move-start', onPanelMoveStart);
panel.movable.removeEventListener('moving', onPanelMoving);
panel.movable.removeEventListener('move-end', onPanelMoveEnd);
});
createEffect(prevWidgetPosition => {
const newWidgetPosition = settings().widgetPosition;
if (newWidgetPosition.x !== prevWidgetPosition.x || newWidgetPosition.y !== prevWidgetPosition.y) {
panel.movable.setPosition(newWidgetPosition.x * window.innerWidth, newWidgetPosition.y * window.innerHeight);
}
return newWidgetPosition;
}, settings().widgetPosition);
const userTemperatureUnit = createMemo(() => TEMPERATURE_UNITS[settings().temperatureUnit]);
const graphicFillPercent = createMemo(prev => {
const forecast$1 = forecast();
if (!forecast$1) {
return prev;
}
const temperatureGradientLength = Math.abs(settings().temperatureGradientMaxCelsius - settings().temperatureGradientMinCelsius);
return forecast$1.current.temperature_2m / temperatureGradientLength;
}, 0);
const currentTemperatureCelsius = createMemo(previousTemperatureCelsius => {
const forecast$1 = forecast();
if (!forecast$1) {
// Preserve previous temperature
return previousTemperatureCelsius;
}
return forecast$1.current.temperature_2m;
}, null);
const graphicColor = createMemo(() => {
if (!(currentTemperatureCelsius() || temperatureGradientHasRedrawn())) {
return null;
}
return sampleTemperatureGradient(currentTemperatureCelsius());
});
return (() => {
var _el$ = _tmpl$5$1(),
_el$2 = _el$.firstChild;
insert(_el$, createComponent(SingleInstanceStyle, {
key: "ThermometerWidget",
children: stylesheet$9
}), _el$2);
insert(_el$2, createComponent(Show, {
get when() {
return error();
},
get children() {
var _el$3 = _tmpl$$5();
createRenderEffect(() => className(_el$3, styles$6['error']));
return _el$3;
}
}), null);
insert(_el$2, createComponent(Show, {
get when() {
return memo(() => !!!loading())() && forecast() != null;
},
get children() {
return [createComponent(Show, {
get when() {
return settings().showClock;
},
get children() {
var _el$4 = _tmpl$2$2();
insert(_el$4, createComponent(ForecastZoneDateClock, {
get forecast() {
return forecast();
}
}));
return _el$4;
}
}), createComponent(Show, {
get when() {
return settings().showTemperature;
},
get children() {
var _el$5 = _tmpl$3$2();
_el$5.firstChild;
insert(_el$5, () => userTemperatureUnit().fromCelsius(forecast().current.temperature_2m), null);
insert(_el$5, () => userTemperatureUnit().unit, null);
return _el$5;
}
}), createComponent(Show, {
get when() {
return settings().showRelativeHumidity;
},
get children() {
var _el$8 = _tmpl$4$2(),
_el$9 = _el$8.firstChild,
_el$1 = _el$9.nextSibling;
_el$1.nextSibling;
insert(_el$8, () => forecast().current.relative_humidity_2m, _el$1);
return _el$8;
}
})];
}
}), null);
insert(_el$, createComponent(ThermometerGraphic, {
get busy() {
return loading();
},
get fillPercent() {
return graphicFillPercent();
},
get color() {
return graphicColor();
},
get ["class"]() {
return styles$6['graphic'];
},
ref(r$) {
var _ref$ = graphicEl;
typeof _ref$ === "function" ? _ref$(r$) : graphicEl = r$;
}
}), null);
createRenderEffect(_p$ => {
var _v$ = {
[styles$6['container']]: true,
[styles$6['is-info-on-the-right']]: isInfoOnTheRight(),
[styles$6['is-being-grabbed']]: isBeingGrabbed()
},
_v$2 = styles$6['info'];
_p$.e = classList(_el$, _v$, _p$.e);
_v$2 !== _p$.t && className(_el$2, _p$.t = _v$2);
return _p$;
}, {
e: undefined,
t: undefined
});
return _el$;
})();
};
var irfTabStyles = {"tab-content":"thermometer-irf-tab-tab-content"};
var stylesheet$6=".thermometer-irf-tab-tab-content *,\n .thermometer-irf-tab-tab-content *::before,\n .thermometer-irf-tab-tab-content *::after {\n box-sizing: border-box;\n }\n";
var styles$3 = {"field-group":"thermometer-SettingsFieldGroup-field-group","field-group__label-container":"thermometer-SettingsFieldGroup-field-group__label-container","field-group__input-container":"thermometer-SettingsFieldGroup-field-group__input-container"};
var stylesheet$5=".thermometer-SettingsFieldGroup-field-group {\n margin-block: 1rem;\n gap: 0.25rem;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n .thermometer-SettingsFieldGroup-field-group label > small {\n color: lightgray;\n display: block;\n }\n\n .thermometer-SettingsFieldGroup-field-group .thermometer-SettingsFieldGroup-field-group__label-container,\n .thermometer-SettingsFieldGroup-field-group .thermometer-SettingsFieldGroup-field-group__input-container {\n width: 100%;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n align-items: center;\n gap: 1ch;\n }\n\n .thermometer-SettingsFieldGroup-field-group .thermometer-SettingsFieldGroup-field-group__input-container {\n justify-content: end;\n white-space: nowrap;\n }\n";
var _tmpl$$4 = /*#__PURE__*/template(`<div><div><label></label></div><div>`);
var SettingsFieldGroup = props => {
return [createComponent(SingleInstanceStyle, {
key: "SettingsFieldGroup",
children: stylesheet$5
}), (() => {
var _el$ = _tmpl$$4(),
_el$2 = _el$.firstChild,
_el$3 = _el$2.firstChild,
_el$4 = _el$2.nextSibling;
insert(_el$3, () => props.label);
insert(_el$4, () => props.children);
createRenderEffect(_p$ => {
var _v$ = styles$3['field-group'],
_v$2 = styles$3['field-group__label-container'],
_v$3 = props.id,
_v$4 = styles$3['field-group__input-container'];
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$2, _p$.t = _v$2);
_v$3 !== _p$.a && setAttribute(_el$3, "for", _p$.a = _v$3);
_v$4 !== _p$.o && className(_el$4, _p$.o = _v$4);
return _p$;
}, {
e: undefined,
t: undefined,
a: undefined,
o: undefined
});
return _el$;
})()];
};
var styles$2 = {"gradient-field-group":"thermometer-SettingsGradientFieldGroup-gradient-field-group","label":"thermometer-SettingsGradientFieldGroup-label","gradient-range":"thermometer-SettingsGradientFieldGroup-gradient-range","gradient-range__input-container":"thermometer-SettingsGradientFieldGroup-gradient-range__input-container","gradient-container":"thermometer-SettingsGradientFieldGroup-gradient-container","stops-container":"thermometer-SettingsGradientFieldGroup-stops-container"};
var stylesheet$4=".thermometer-SettingsGradientFieldGroup-gradient-field-group {\n position: relative;\n margin-block: 1rem;\n gap: 0.25rem;\n display: flex;\n justify-content: space-between;\n flex-direction: column;\n}\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-label {\n width: 100%;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n align-items: center;\n gap: 1ch;\n }\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group canvas {\n width: 100%;\n height: 20px;\n }\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-gradient-range {\n --inputs-size: 8ch;\n --inputs-size-padding: 3ch;\n --inputs-full-size: calc(var(--inputs-size) + var(--inputs-size-padding));\n\n display: flex;\n justify-content: space-between;\n\n background:\n /* Dotted line */\n repeating-linear-gradient(to right, #b9b9b973 0 3px, transparent 6px 9px),\n \n linear-gradient(\n to right,\n black 0%,\n black var(--inputs-full-size),\n transparent calc(var(--inputs-full-size) + 10%),\n transparent calc(90% - var(--inputs-full-size)),\n black calc(100% - var(--inputs-full-size)),\n black 100%\n );\n background-size: auto 3px;\n background-blend-mode: multiply;\n background-repeat: repeat-x;\n background-position: center;\n }\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-gradient-range .thermometer-SettingsGradientFieldGroup-gradient-range__input-container {\n display: flex;\n align-items: center;\n gap: 0.5ch;\n }\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-gradient-range .thermometer-SettingsGradientFieldGroup-gradient-range__input-container input {\n width: var(--inputs-size);\n text-align: right;\n\n -moz-appearance: textfield;\n appearance: textfield;\n }\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-gradient-range .thermometer-SettingsGradientFieldGroup-gradient-range__input-container input::-webkit-inner-spin-button,\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-gradient-range .thermometer-SettingsGradientFieldGroup-gradient-range__input-container input::-webkit-inner-spin-button {\n display: none;\n }\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-gradient-container {\n position: relative;\n padding-top: 0.33rem;\n margin-inline: 0.5rem;\n cursor: pointer;\n }\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-stops-container {\n --stop-height: 0.85rem;\n --stop-tip-height: 7px;\n --stop-border-size: 2px;\n\n position: relative;\n min-height: calc(var(--stop-tip-height) + var(--stop-height) + var(--stop-border-size)*2);\n }\n";
var styles$1 = {"gradient-marker":"thermometer-GradientMarker-gradient-marker","gradient-marker--current":"thermometer-GradientMarker-gradient-marker--current","gradient-marker--cursor":"thermometer-GradientMarker-gradient-marker--cursor"};
var stylesheet$3=".thermometer-GradientMarker-gradient-marker {\n --marker-color: pink;\n\n position: absolute;\n top: 0;\n translate: -50% -133%;\n text-align: center;\n white-space: nowrap;\n font-size: 75%;\n text-shadow: 0 0 3px black;\n pointer-events: none;\n}\n\n .thermometer-GradientMarker-gradient-marker.thermometer-GradientMarker-gradient-marker--current {\n --marker-color: gray;\n }\n\n .thermometer-GradientMarker-gradient-marker.thermometer-GradientMarker-gradient-marker--cursor {\n --marker-color: red;\n }\n\n .thermometer-GradientMarker-gradient-marker::after {\n content: '';\n position: absolute;\n translate: -50% 100%;\n left: 50%;\n bottom: 0;\n width: 1rem;\n height: 0.5rem;\n background-color: var(--marker-color);\n clip-path: polygon(50% 100%, 0 0, 100% 0);\n }\n";
var _tmpl$$3 = /*#__PURE__*/template(`<span>`);
let GradientMarkerType = /*#__PURE__*/function (GradientMarkerType) {
GradientMarkerType["CURRENT"] = "current";
GradientMarkerType["CURSOR"] = "cursor";
return GradientMarkerType;
}({});
var GradientMarker = props => {
const temperaturePercent = () => zeroOne(props.temperatureCelsius, settings().temperatureGradientMinCelsius, settings().temperatureGradientMaxCelsius);
const prettyTemperatureValue = value => {
switch (props.type) {
case GradientMarkerType.CURSOR:
{
// Round to closest multiple of 0.5
return (Math.round(value * 2) / 2).toFixed(1);
}
default:
{
return Math.round(value).toFixed(0);
}
}
};
const userTemperatureUnit = () => TEMPERATURE_UNITS[settings().temperatureUnit];
return [createComponent(SingleInstanceStyle, {
key: "GradientMarker",
children: stylesheet$3
}), (() => {
var _el$ = _tmpl$$3();
insert(_el$, () => prettyTemperatureValue(userTemperatureUnit().fromCelsius(props.temperatureCelsius)), null);
insert(_el$, () => userTemperatureUnit().unit, null);
createRenderEffect(_p$ => {
var _v$ = {
[styles$1['gradient-marker']]: true,
[styles$1[`gradient-marker--${props.type}`]]: true
},
_v$2 = `${temperaturePercent() * 100}%`;
_p$.e = classList(_el$, _v$, _p$.e);
_v$2 !== _p$.t && setStyleProperty(_el$, "left", _p$.t = _v$2);
return _p$;
}, {
e: undefined,
t: undefined
});
return _el$;
})()];
};
var styles = {"gradient-stop":"thermometer-GradientColorStop-gradient-stop"};
var stylesheet$2=".thermometer-GradientColorStop-gradient-stop {\n position: absolute;\n background-color: transparent;\n translate: -50% 0;\n height: var(--stop-height);\n aspect-ratio: 0.9;\n display: inline-block;\n border-radius: 4px 4px 2px 2px;\n border: var(--stop-border-size) solid white;\n margin-top: var(--stop-tip-height);\n cursor: grab;\n}\n\n .thermometer-GradientColorStop-gradient-stop::before {\n content: '';\n background-color: white;\n width: var(--stop-tip-height);\n aspect-ratio: 1;\n position: absolute;\n top: 0;\n left: 50%;\n translate: -50% -100%;\n clip-path: polygon(50% 0, 100% 100%, 0 100%);\n }\n\n .thermometer-GradientColorStop-gradient-stop input[type='color'] {\n pointer-events: none;\n width: 1px;\n aspect-ratio: 1;\n opacity: 0.01;\n }\n";
var _tmpl$$2 = /*#__PURE__*/template(`<div><input type=color>`);
var GradientColorStop = props => {
let stopEl;
let colorInputEl;
let draggingState = null;
const onMouseDown = event => {
event.preventDefault();
if (event.button !== 0 /* left click */) {
return;
}
draggingState = {
startPos: {
x: event.clientX,
y: event.clientY
}
};
};
const onDocumentMouseUp = event => {
if (draggingState == null) {
return;
}
event.preventDefault();
if (Math.abs(draggingState.startPos.x - event.clientX) + Math.abs(draggingState.startPos.y - event.clientY) < 5) {
colorInputEl.click();
}
draggingState = null;
};
const onDocumentMouseMove = event => {
if (draggingState == null) {
return;
}
event.preventDefault();
const gradientStopBoundingBox = stopEl.parentElement.getBoundingClientRect();
const percent = (event.clientX - gradientStopBoundingBox.left) / gradientStopBoundingBox.width;
const clampedPercent = Math.max(0, Math.min(percent, 1));
props.onMove(clampedPercent);
};
const onContextMenu = event => {
event.preventDefault();
props.onDelete();
};
const onColorInputChange = event => {
event.preventDefault();
props.onChange(colorInputEl.value);
};
onMount(() => {
document.addEventListener('mouseup', onDocumentMouseUp);
document.addEventListener('mousemove', onDocumentMouseMove);
});
onCleanup(() => {
document.removeEventListener('mouseup', onDocumentMouseUp);
document.removeEventListener('mousemove', onDocumentMouseMove);
});
return [createComponent(SingleInstanceStyle, {
key: "GradientColorStop",
children: stylesheet$2
}), (() => {
var _el$ = _tmpl$$2(),
_el$2 = _el$.firstChild;
var _ref$ = stopEl;
typeof _ref$ === "function" ? use(_ref$, _el$) : stopEl = _el$;
addEventListener(_el$, "contextmenu", onContextMenu);
addEventListener(_el$, "mousedown", onMouseDown);
var _ref$2 = colorInputEl;
typeof _ref$2 === "function" ? use(_ref$2, _el$2) : colorInputEl = _el$2;
addEventListener(_el$2, "input", onColorInputChange);
createRenderEffect(_p$ => {
var _v$ = styles['gradient-stop'],
_v$2 = `${props.percent * 100}%`,
_v$3 = props.color,
_v$4 = Math.round(props.percent * 100);
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
_v$2 !== _p$.t && setStyleProperty(_el$, "left", _p$.t = _v$2);
_v$3 !== _p$.a && setStyleProperty(_el$, "background-color", _p$.a = _v$3);
_v$4 !== _p$.o && setStyleProperty(_el$, "z-index", _p$.o = _v$4);
return _p$;
}, {
e: undefined,
t: undefined,
a: undefined,
o: undefined
});
createRenderEffect(() => _el$2.value = props.color);
return _el$;
})()];
};
var _tmpl$$1 = /*#__PURE__*/template(`<div><input type=number>`),
_tmpl$2$1 = /*#__PURE__*/template(`<div>`),
_tmpl$3$1 = /*#__PURE__*/template(`<div><div>`),
_tmpl$4$1 = /*#__PURE__*/template(`<div><div><label>Temperature Gradient</label><button><img src=https://www.svgrepo.com/show/511181/undo.svg>`);
const GradientRangeInput = props => {
const userTemperatureUnit = () => TEMPERATURE_UNITS[settings().temperatureUnit];
const onChange = event => {
const numberValue = parseFloat(event.currentTarget.value);
if (Number.isNaN(numberValue)) {
return;
}
props.onChange(userTemperatureUnit().toCelsius(numberValue));
};
return (() => {
var _el$ = _tmpl$$1(),
_el$2 = _el$.firstChild;
addEventListener(_el$2, "change", onChange);
insert(_el$, () => userTemperatureUnit().unit, null);
createRenderEffect(_p$ => {
var _v$ = styles$2['gradient-range__input-container'],
_v$2 = props.id;
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
_v$2 !== _p$.t && setAttribute(_el$2, "id", _p$.t = _v$2);
return _p$;
}, {
e: undefined,
t: undefined
});
createRenderEffect(() => _el$2.value = userTemperatureUnit().fromCelsius(props.valueCelsius));
return _el$;
})();
};
const GradientRange = () => {
const onChangeMin = newMinTemperatureCelsius => saveSettings(previousSettings => _extends({}, previousSettings, {
temperatureGradientMinCelsius: Math.min(newMinTemperatureCelsius, previousSettings.temperatureGradientMaxCelsius),
temperatureGradientMaxCelsius: Math.max(newMinTemperatureCelsius, previousSettings.temperatureGradientMaxCelsius)
}));
const onChangeMax = newMaxTemperatureCelsius => saveSettings(previousSettings => _extends({}, previousSettings, {
temperatureGradientMinCelsius: Math.min(previousSettings.temperatureGradientMinCelsius, newMaxTemperatureCelsius),
temperatureGradientMaxCelsius: Math.max(previousSettings.temperatureGradientMinCelsius, newMaxTemperatureCelsius)
}));
return (() => {
var _el$3 = _tmpl$2$1();
insert(_el$3, createComponent(GradientRangeInput, {
id: `${MOD_DOM_SAFE_PREFIX}gradient-temperature-min`,
get valueCelsius() {
return settings().temperatureGradientMinCelsius;
},
onChange: onChangeMin
}), null);
insert(_el$3, createComponent(GradientRangeInput, {
id: `${MOD_DOM_SAFE_PREFIX}gradient-temperature-max`,
get valueCelsius() {
return settings().temperatureGradientMaxCelsius;
},
onChange: onChangeMax
}), null);
createRenderEffect(() => className(_el$3, styles$2['gradient-range']));
return _el$3;
})();
};
const GradientContainer = () => {
const [hoveringTemperature, setHoveringTemperature] = createSignal(null);
const onGradientContainerMouseMove = event => {
const {
left: containerClientX,
width: containerWidth
} = event.currentTarget.getBoundingClientRect();
const containerOffsetX = event.clientX - containerClientX;
const percent = containerOffsetX / containerWidth;
setHoveringTemperature(temperatureAtGradient(percent));
};
const onGradientContainerMouseLeave = () => {
setHoveringTemperature(null);
};
const onGradientContainerDoubleClick = event => {
event.preventDefault();
event.stopPropagation();
const percent = event.offsetX / event.currentTarget.clientWidth;
const color = sampleTemperatureGradient(temperatureAtGradient(percent));
const newStop = {
percent,
color
};
saveSettings(previousSettings => _extends({}, previousSettings, {
temperatureGradient: previousSettings.temperatureGradient.concat(newStop)
}));
};
return (() => {
var _el$4 = _tmpl$3$1(),
_el$5 = _el$4.firstChild;
addEventListener(_el$4, "dblclick", onGradientContainerDoubleClick);
addEventListener(_el$4, "mouseleave", onGradientContainerMouseLeave);
addEventListener(_el$4, "mousemove", {
handleEvent: onGradientContainerMouseMove,
capture: true
});
insert(_el$4, createComponent(Show, {
get when() {
return forecast();
},
get children() {
return createComponent(GradientMarker, {
get type() {
return GradientMarkerType.CURRENT;
},
get temperatureCelsius() {
return forecast().current.temperature_2m;
}
});
}
}), _el$5);
insert(_el$4, createComponent(Show, {
get when() {
return hoveringTemperature();
},
get children() {
return createComponent(GradientMarker, {
get type() {
return GradientMarkerType.CURSOR;
},
get temperatureCelsius() {
return hoveringTemperature();
}
});
}
}), _el$5);
insert(_el$4, temperatureGradientCanvas, _el$5);
insert(_el$5, createComponent(Index, {
get each() {
return settings().temperatureGradient;
},
children: (stop, index) => {
const onMove = newPercent => {
saveSettings(previousSettings => _extends({}, previousSettings, {
temperatureGradient: previousSettings.temperatureGradient.with(index, _extends({}, stop(), {
percent: newPercent
}))
}));
};
const onColorChange = newColor => {
saveSettings(previousSettings => _extends({}, previousSettings, {
temperatureGradient: previousSettings.temperatureGradient.with(index, _extends({}, stop(), {
color: newColor
}))
}));
};
const onDelete = () => {
saveSettings(previousSettings => _extends({}, previousSettings, {
temperatureGradient: previousSettings.temperatureGradient.toSpliced(index, 1)
}));
};
return createComponent(GradientColorStop, {
get color() {
return stop().color;
},
get percent() {
return stop().percent;
},
onMove: onMove,
onChange: onColorChange,
onDelete: onDelete
});
}
}));
createRenderEffect(_p$ => {
var _v$3 = styles$2['gradient-container'],
_v$4 = styles$2['stops-container'];
_v$3 !== _p$.e && className(_el$4, _p$.e = _v$3);
_v$4 !== _p$.t && className(_el$5, _p$.t = _v$4);
return _p$;
}, {
e: undefined,
t: undefined
});
return _el$4;
})();
};
var SettingsGradientFieldGroup = () => {
const id = `${MOD_DOM_SAFE_PREFIX}temperature-gradient`;
const onResetButtonClick = () => {
if (!confirm(['This will reset the thermometer gradient range and color stops to their default values.', 'Are you sure you want to continue?'].join('\n'))) {
return;
}
saveSettings(previousSettings => _extends({}, previousSettings, getDefaultTemperatureGradientSettings()));
};
return (() => {
var _el$6 = _tmpl$4$1(),
_el$7 = _el$6.firstChild,
_el$8 = _el$7.firstChild,
_el$9 = _el$8.nextSibling;
insert(_el$6, createComponent(SingleInstanceStyle, {
key: "SettingsGradientFieldGroup",
children: stylesheet$4
}), _el$7);
setAttribute(_el$8, "for", id);
addEventListener(_el$9, "click", onResetButtonClick);
insert(_el$6, createComponent(GradientRange, {}), null);
insert(_el$6, createComponent(GradientContainer, {}), null);
createRenderEffect(_p$ => {
var _v$5 = styles$2['gradient-field-group'],
_v$6 = styles$2['label'];
_v$5 !== _p$.e && className(_el$6, _p$.e = _v$5);
_v$6 !== _p$.t && className(_el$7, _p$.t = _v$6);
return _p$;
}, {
e: undefined,
t: undefined
});
return _el$6;
})();
};
var irfPanelDesignStyles = {"toggle":"thermometer-irf-panel-design-toggle"};
var stylesheet$1="/*\n * Copied from <https://raw.githubusercontent.com/Mikarific/InternetRoadtripFramework/c8fbacda2f65d7ff76b9ad94d155ad2c20829abd/src/lib/panel.module.css>.\n * We cannot use IRF.ui.panel.styles.* because those classes are not accessible from inside the shadow root we create for our settings panel.\n *\n * TODO(netux): Consider a better solution for IRF 0.5.0-beta?\n */\n\n.thermometer-irf-panel-design-toggle {\n --toggle-offset: -1.5rem;\n width: 3rem;\n height: 1.5rem;\n margin: 0;\n box-shadow:\n inset var(--toggle-offset) 0 0 2px #000,\n inset 0 0 0 2px #000;\n transition-property: box-shadow;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n transition-duration: 200ms;\n transition-timing-function: cubic-bezier(0, 0, 0.2, 1);\n -webkit-appearance: none;\n appearance: none;\n border: 1px solid #848e95;\n border-radius: 9999px;\n background: #848e95;\n cursor: pointer;\n}\n.thermometer-irf-panel-design-toggle:checked {\n --toggle-offset: 1.5rem;\n background: #fff;\n border-color: #fff;\n}\n.thermometer-irf-panel-design-toggle:disabled {\n cursor: not-allowed;\n opacity: 0.3;\n}\n\n.thermometer-irf-panel-design-slider {\n width: 100%;\n height: 1.5rem;\n margin: 0;\n border-radius: 0.75rem;\n background-color: #0000;\n border: none;\n overflow: hidden;\n appearance: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n cursor: pointer;\n vertical-align: middle;\n}\n\n.thermometer-irf-panel-design-slider:focus {\n outline: none;\n}\n.thermometer-irf-panel-design-slider:focus-visible {\n outline-offset: 2px;\n outline: 2px solid;\n}\n.thermometer-irf-panel-design-slider::-webkit-slider-runnable-track {\n background-color: #41474b;\n border-radius: 0.5rem;\n width: 100%;\n height: 0.75rem;\n}\n@media (forced-colors: active) {\n .thermometer-irf-panel-design-slider::-webkit-slider-runnable-track {\n border: 1px solid;\n }\n .thermometer-irf-panel-design-slider::-moz-range-track {\n border: 1px solid;\n }\n}\n.thermometer-irf-panel-design-slider::-webkit-slider-thumb {\n box-sizing: border-box;\n border-radius: 0.75rem;\n height: 1.5rem;\n width: 1.5rem;\n border: 0.25rem solid;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n color: #fff;\n box-shadow:\n 0 0 0 2rem #000 inset,\n -100.75rem 0 0 100rem;\n background-color: #fff;\n position: relative;\n top: 50%;\n transform: translateY(-50%);\n}\n.thermometer-irf-panel-design-slider::-moz-range-track {\n background-color: #41474b;\n border-radius: 0.5rem;\n width: 100%;\n height: 0.75rem;\n}\n.thermometer-irf-panel-design-slider::-moz-range-thumb {\n box-sizing: border-box;\n border-radius: 0.75rem;\n height: 1.5rem;\n width: 1.5rem;\n border: 0.25rem solid;\n color: #fff;\n box-shadow:\n 0 0 0 2rem #000 inset,\n -100.75rem 0 0 100rem;\n background-color: #fff;\n position: relative;\n top: 50%;\n}\n.thermometer-irf-panel-design-slider:disabled {\n cursor: not-allowed;\n opacity: 0.3;\n}\n";
var stylesheet="input:is(:not([type]), [type='text'], [type='number']),\nselect {\n --padding-inline: 0.5rem;\n --border-radius: 0.8rem;\n --border-color: #848e95;\n\n width: calc(100% - var(--padding-inline)*2);\n min-height: 1.5rem;\n margin: 0;\n padding-inline: var(--padding-inline);\n color: white;\n background: transparent;\n border: 1px solid var(--border-color);\n font-size: 100%;\n border-radius: var(--border-radius);\n}\n\noption {\n background-color: black;\n}\n\n@supports (appearance: base-select) {\n select,\n ::picker(select) {\n appearance: base-select;\n font-size: 0.9rem;\n }\n\n select::picker-icon {\n scale: 0.9;\n margin-right: 0.125rem;\n }\n\n select:open {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-color: transparent;\n }\n\n ::picker(select) {\n margin-top: -1px;\n border: none;\n background-color: black;\n border-bottom-left-radius: var(--border-radius);\n border-bottom-right-radius: var(--border-radius);\n border: 1px solid var(--border-color);\n border-top-color: transparent;\n }\n\n option {\n color: white;\n background-color: black;\n }\n\n option::checkmark {\n display: none;\n }\n\n option:checked {\n background-color: rgb(255 255 255 / 10%);\n }\n\n option:hover {\n background-color: rgb(255 255 255 / 20%);\n }\n}\n\nbutton {\n padding: 0.25rem;\n margin-left: 0.125rem;\n gap: 0.25rem;\n cursor: pointer;\n border: none;\n font-size: 0.9rem;\n align-items: center;\n justify-content: center;\n background-color: white;\n display: inline-flex;\n}\n\nbutton:hover {\n background-color: #d3d3d3;\n }\n\nbutton > img {\n width: 1rem;\n aspect-ratio: 1;\n }\n";
var _tmpl$ = /*#__PURE__*/template(`<select>`),
_tmpl$2 = /*#__PURE__*/template(`<option>`),
_tmpl$3 = /*#__PURE__*/template(`<input type=checkbox>`),
_tmpl$4 = /*#__PURE__*/template(`<h2>Clock`),
_tmpl$5 = /*#__PURE__*/template(`<h2>Temperature`),
_tmpl$6 = /*#__PURE__*/template(`<h2>Relative Humidity`),
_tmpl$7 = /*#__PURE__*/template(`<button>Reset widget position`);
const SettingsTemperatureUnitFieldGroup = () => {
const label = 'Temperature Unit';
const id = `${MOD_DOM_SAFE_PREFIX}temperature-unit`;
const onChange = async event => {
await saveSettings({
temperatureUnit: event.currentTarget.value
});
};
return createComponent(SettingsFieldGroup, {
id: id,
label: label,
get children() {
var _el$ = _tmpl$();
addEventListener(_el$, "change", onChange);
setAttribute(_el$, "id", id);
insert(_el$, createComponent(For, {
get each() {
return Object.entries(TEMPERATURE_UNITS);
},
children: ([value, {
label
}]) => (() => {
var _el$2 = _tmpl$2();
_el$2.value = value;
insert(_el$2, label);
return _el$2;
})()
}));
createRenderEffect(() => _el$.value = settings().temperatureUnit);
return _el$;
}
});
};
const SettingsToggleFieldGroup = function (props) {
props = mergeProps({
inverted: false,
disabled: false
}, props);
const onChange = async event => {
const checked = event.currentTarget.checked;
await saveSettings({
[props.field]: props.inverted ? !checked : checked
});
};
return createComponent(SettingsFieldGroup, {
get id() {
return props.id;
},
get label() {
return props.label;
},
get children() {
var _el$3 = _tmpl$3();
addEventListener(_el$3, "change", onChange);
createRenderEffect(_p$ => {
var _v$ = props.id,
_v$2 = irfPanelDesignStyles['toggle'],
_v$3 = props.disabled;
_v$ !== _p$.e && setAttribute(_el$3, "id", _p$.e = _v$);
_v$2 !== _p$.t && className(_el$3, _p$.t = _v$2);
_v$3 !== _p$.a && (_el$3.disabled = _p$.a = _v$3);
return _p$;
}, {
e: undefined,
t: undefined,
a: undefined
});
createRenderEffect(() => _el$3.checked = props.inverted ? !settings()[props.field] : settings()[props.field]);
return _el$3;
}
});
};
var SettingsTab = () => {
return [createComponent(SingleInstanceStyle, {
key: "SettingsTab",
children: stylesheet
}), createComponent(SingleInstanceStyle, {
key: "IRFTabExported",
children: stylesheet$1
}), _tmpl$4(), createComponent(SettingsToggleFieldGroup, {
id: `${MOD_DOM_SAFE_PREFIX}show-clock`,
label: "Show Clock",
field: "showClock"
}), createComponent(SettingsToggleFieldGroup, {
id: `${MOD_DOM_SAFE_PREFIX}time-24-hour`,
label: "Use AM/PM",
field: "time24Hours",
inverted: true,
get disabled() {
return !settings().showClock;
}
}), createComponent(SettingsToggleFieldGroup, {
id: `${MOD_DOM_SAFE_PREFIX}time-include-seconds`,
label: "Show Seconds",
field: "timeIncludeSeconds",
get disabled() {
return !settings().showClock;
}
}), _tmpl$5(), createComponent(SettingsToggleFieldGroup, {
id: `${MOD_DOM_SAFE_PREFIX}show-temperature`,
label: "Show Temperature",
field: "showTemperature"
}), createComponent(SettingsTemperatureUnitFieldGroup, {}), createComponent(SettingsGradientFieldGroup, {}), _tmpl$6(), createComponent(SettingsToggleFieldGroup, {
id: `${MOD_DOM_SAFE_PREFIX}show-relative-humidity`,
label: "Show Relative Humidity",
field: "showRelativeHumidity"
}), (() => {
var _el$7 = _tmpl$7();
addEventListener(_el$7, "click", async () => {
await saveSettings({
widgetPosition: DEFAULT_WIDGET_POSITION
});
});
return _el$7;
})()];
};
function createWidget() {
const panel = makePanel({
element: Thermometer,
zIndex: 200
});
setPanel(panel);
panel.show();
panel.movable.setPosition(settings().widgetPosition.x * window.innerWidth, settings().widgetPosition.y * window.innerHeight);
}
function createIrfTab() {
const irfTab = IRF.ui.panel.createTabFor(_extends({}, GM.info, {
script: _extends({}, GM.info.script, {
name: MOD_NAME
})
}), {
tabName: MOD_NAME,
className: irfTabStyles['tab-content'],
style: stylesheet$6
});
render(
// <ShadowRooted> is a workaround for IRF not having per-tab stylesheet isolation.
// TODO(netux): remove <ShadowRooted> for IRF v0.5.0-beta
() => createComponent(ShadowRooted, {
get children() {
return createComponent(SettingsTab, {});
}
}), irfTab.container);
}
async function tickForecast() {
const containerVDOM = await IRF.vdom.container;
setForecastLoading();
let forecast;
try {
forecast = await fetchForecast([containerVDOM.state.currentCoords.lat, containerVDOM.state.currentCoords.lng]);
} catch (error) {
setForecastLoadFailure(error);
console.error(MOD_LOG_PREFIX, 'Could not fetch forecast', error);
return;
}
console.debug(MOD_LOG_PREFIX, 'New forecast received:', forecast);
setForecastLoadSuccess(forecast);
}
createWidget();
createIrfTab();
waitForCoordinatesToBeSetAtLeastOnce.then(() => {
setInterval(tickForecast, 15 * 60000 /* every 15 minutes */);
tickForecast();
});
})(IRF, VM);