// ==UserScript==
// @name UnitFlip: Inline Unit Converter
// @namespace https://greasyfork.org/en/users/1451802
// @version 1.0
// @description Double‑click measurements to instantly convert between metric and imperial units (no APIs)
// @author NormalRandomPeople (https://github.com/NormalRandomPeople)
// @match *://*/*
// @icon https://www.svgrepo.com/show/256974/exchange.svg
// @grant none
// @compatible chrome
// @compatible firefox
// @compatible opera
// @compatible edge
// @compatible brave
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const conv = {
// LENGTH: metric → imperial
mm: v => `${v} mm → ${(v/25.4).toFixed(2)} in`, millimeter: v => conv.mm(v),
cm: v => `${v} cm → ${(v/2.54).toFixed(2)} in`, centimeter: v => conv.cm(v),
m: v => `${v} m → ${(v*3.28084).toFixed(2)} ft`, meter: v => conv.m(v),
km: v => `${v} km → ${(v*0.621371).toFixed(2)} mi`, kilometer: v => conv.km(v),
// LENGTH: imperial → metric
in: v => `${v} in → ${(v*2.54).toFixed(2)} cm`, inch: v => conv.in(v),
ft: v => `${v} ft → ${(v*0.3048).toFixed(2)} m`, foot: v => conv.ft(v), feet: v => conv.ft(v),
yd: v => `${v} yd → ${(v*0.9144).toFixed(2)} m`, yard: v => conv.yd(v), yards: v => conv.yd(v),
mi: v => `${v} mi → ${(v*1.60934).toFixed(2)} km`, mile: v => conv.mi(v), miles: v => conv.mi(v),
nmi: v => `${v} nmi → ${(v*1.852).toFixed(2)} km`, nauticalmile: v => conv.nmi(v), nauticalmiles: v => conv.nmi(v),
// MASS: metric → imperial
mg: v => `${v} mg → ${(v/28.3495).toFixed(2)} oz`, milligram: v => conv.mg(v),
g: v => `${v} g → ${(v/28.3495).toFixed(2)} oz`, gram: v => conv.g(v),
kg: v => `${v} kg → ${(v*2.20462).toFixed(2)} lb`, kilogram: v => conv.kg(v),
t: v => `${v} t → ${(v*2204.62).toFixed(2)} lb`, ton: v => conv.t(v),
// MASS: imperial → metric
oz: v => `${v} oz → ${(v*28.3495).toFixed(2)} g`, ounce: v => conv.oz(v), ounces: v => conv.oz(v),
lb: v => `${v} lb → ${(v*0.453592).toFixed(2)} kg`, pound: v => conv.lb(v), pounds: v => conv.lb(v),
st: v => `${v} st → ${(v*6.35029).toFixed(2)} kg`, stone: v => conv.st(v), stones: v => conv.st(v),
// VOLUME: metric → imperial
ml: v => `${v} mL → ${(v/29.5735).toFixed(2)} fl oz`, milliliter: v => conv.ml(v),
l: v => `${v} L → ${(v*0.264172).toFixed(2)} gal`, liter: v => conv.l(v),
m3: v => `${v} m³ → ${(v*35.3147).toFixed(2)} ft³`, 'm^3': v => conv.m3(v),
// VOLUME: imperial → metric
'fl oz': v => `${v} fl oz → ${(v*29.5735).toFixed(2)} mL`, fluidounce: v => conv['fl oz'](v),
cup: v => `${v} cup → ${(v*236.588).toFixed(2)} mL`, cups: v => conv.cup(v),
pt: v => `${v} pt → ${(v*473.176).toFixed(2)} mL`, pint: v => conv.pt(v), pints: v => conv.pt(v),
qt: v => `${v} qt → ${(v*946.353).toFixed(2)} mL`, quart: v => conv.qt(v), quarts: v => conv.qt(v),
gal: v => `${v} gal → ${(v*3785.41).toFixed(2)} mL`, gallon: v => conv.gal(v), gallons: v => conv.gal(v),
// AREA: metric → imperial
m2: v => `${v} m² → ${(v*10.7639).toFixed(2)} ft²`, 'm^2': v => conv.m2(v), sqm: v => conv.m2(v),
km2: v => `${v} km² → ${(v*0.386102).toFixed(2)} mi²`, 'km^2': v => conv.km2(v), sqkm: v => conv.km2(v),
// AREA: imperial → metric
ft2: v => `${v} ft² → ${(v*0.092903).toFixed(2)} m²`, 'ft^2': v => conv.ft2(v), sqft: v => conv.ft2(v),
in2: v => `${v} in² → ${(v*6.4516).toFixed(2)} cm²`, 'in^2': v => conv.in2(v), sqin: v => conv.in2(v),
ac: v => `${v} ac → ${(v*4046.86).toFixed(2)} m²`, acre: v => conv.ac(v), acres: v => conv.ac(v),
ha: v => `${v} ha → ${(v*10000).toFixed(2)} m²`, hectare: v => conv.ha(v), hectares: v => conv.ha(v),
mi2: v => `${v} mi² → ${(v*2.58999).toFixed(2)} km²`, 'mi^2': v => conv.mi2(v), sqmi: v => conv.mi2(v),
// TEMPERATURE
c: v => `${v.toFixed(1)} °C → ${(v*9/5+32).toFixed(1)} °F`, '°c': v => conv.c(v), degc: v => conv.c(v),
f: v => `${v.toFixed(1)} °F → ${((v-32)*5/9).toFixed(1)} °C`, '°f': v => conv.f(v), degf: v => conv.f(v),
k: v => `${v.toFixed(1)} K → ${(v-273.15).toFixed(1)} °C`, '°k': v => conv.k(v), degk: v => conv.k(v)
};
let tooltip = null;
function removeTooltip() { if (tooltip) { tooltip.remove(); tooltip = null; }}
function showTooltip(txt, x, y) {
removeTooltip();
tooltip = document.createElement('div');
tooltip.textContent = txt;
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
Object.assign(tooltip.style, {
position: 'absolute', top: `${y+12}px`, left: `${x+12}px`,
padding: '6px 10px', background: isDark ? 'rgba(30,30,30,0.95)' : 'rgba(255,255,225,0.95)',
color: isDark ? '#f1f1f1' : '#222', border: `1px solid ${isDark ? '#555' : '#888'}`,
borderRadius: '5px', boxShadow: isDark ? '0 2px 6px rgba(0,0,0,0.5)' : '0 2px 6px rgba(0,0,0,0.2)',
fontSize: '13px', fontFamily: 'sans-serif', lineHeight: '1.4', zIndex: 2147483647,
whiteSpace: 'nowrap', pointerEvents: 'none'
});
document.body.appendChild(tooltip);
}
function convert(sel) {
const hMatch = sel.match(/^(\d+)'\s*(\d+)(?:"|$)/);
if (hMatch) {
const ft = parseInt(hMatch[1], 10);
const inch = parseInt(hMatch[2], 10);
const totalIn = ft * 12 + inch;
const cm = (totalIn * 2.54).toFixed(2);
return `${ft}'${inch}" → ${cm} cm`;
}
sel = sel.replace(/,/g, '.').replace(/²/g, '2').trim();
const m = sel.match(/^(-?\d+(?:\.\d+)?)(?:\s*)([\w°^]+(?: [\w^]+)?)$/);
if (!m) return null;
const v = parseFloat(m[1]), u = m[2].toLowerCase();
return conv[u] ? conv[u](v) : null;
}
document.addEventListener('dblclick', e => {
try {
const txt = window.getSelection().toString(); if (!txt) return;
const res = convert(txt); if (res) showTooltip(res, e.pageX, e.pageY);
} catch (err) { console.error(err); removeTooltip(); }
});
document.addEventListener('click', e => { if (tooltip && !tooltip.contains(e.target)) removeTooltip(); });
document.addEventListener('keydown', e => { if (e.key === 'Escape') removeTooltip(); });
})();