Skip to content

Instantly share code, notes, and snippets.

@jojobyte
Created February 10, 2026 00:20
Show Gist options
  • Select an option

  • Save jojobyte/19bd62d9fee091351e8efcc9314f1929 to your computer and use it in GitHub Desktop.

Select an option

Save jojobyte/19bd62d9fee091351e8efcc9314f1929 to your computer and use it in GitHub Desktop.
Bookmarklet to invert the colors of a website with Controls. One click inverts, second click removes the inversion. Paste as the URL inside a bookmark.
javascript:void function(){
let b='#fff',toggle=true,i=95,ii=0,ia=100,h=180,hi=0,ha=360;
const d=document,ce=(n)=>d.createElement(n),q=d.querySelectorAll(".dplgngr"),s=ce("style"),c=ce("form"),t='Doppelgänger';
if(q?.length)return q.forEach(a=>a.remove());
const invertStyles = (b,i,h) => `html { background-color: ${b}; }
html,img,video,iframe { filter: invert(${i}%) hue-rotate(${h}deg); }`;
const styles = (b,i,h,t) => `${t ? invertStyles(b,i,h) : ''}
.dplgngr fieldset { border: 0 solid transparent; margin-top: 1em; padding: 0; display: grid; grid-template-areas: "l l" "r n"; grid-template-columns: 2fr 1fr; column-gap: 1em; }
.dplgngr label { grid-area: l; }
.dplgngr input { padding: 0; grid-area: r; }
.dplgngr input[type="number"] { padding: 2px 4px; grid-area: n; }
.dplgngr.ctrls { background: #f9f9f9; color: #111; position: fixed; display: block; z-index: 100000; top: 0; right: 0; bottom: auto; width: 300px; height: auto; padding: 1em; }
`;
function createSignal(initialValue) {
let _value = initialValue;
let _last = _value;
const subs = [];
function pub() {
for (let s of subs) {
s && s(_value, _last);
}
}
return {
get value() { return _value; },
set value(v) {
_last = _value;
_value = v;
pub();
},
on: s => {
const i = subs.push(s)-1;
return () => { subs[i] = 0; };
},
};
}
function template(html, elementOnly = true) {
let node;
const create = () => {
const t = ce('template');
t.innerHTML = html;
return t.content[elementOnly ? 'firstElementChild' : 'firstChild'];
};
return () => (node || (node = create())).cloneNode(true);
}
function setupFields(element, initSignalVal) {
let fieldType, style = createSignal(initSignalVal);
let inputs = Object.fromEntries([...element.querySelectorAll('input')].map(e => [e.type,e]));
const setNumericValue = val => (val >= 0 ? val : 0);
const render = (v, p) => {
console.log('setupFields render', { fieldType, v, });
if (!fieldType && !inputs.checkbox) {
inputs.number.value = v;
inputs.range.value = v;
}
if (fieldType === 'number') {
inputs.range.value = v;
}
if (fieldType === 'range') {
inputs.number.value = v;
}
if (inputs.range?.name === 'hue') {
h = v;
}
if (inputs.range?.name === 'invert') {
i = v;
}
if (fieldType === 'checkbox') {
toggle = inputs.checkbox.checked;
}
s.innerText=styles(b, i, h, toggle);
fieldType = undefined;
};
const listener = (event) => {
fieldType = event.target.type;
let v = fieldType === 'checkbox' ? event.target.checked : setNumericValue(event.target.value);
console.log('setupFields listener', { fieldType, v, event });
style.value = v;
};
const styleOff = style.on(render);
const unsub = () => {
styleOff();
inputs.checkbox?.removeEventListener('input', listener);
inputs.number?.removeEventListener('input', listener);
inputs.range?.removeEventListener('input', listener);
console.log('setupFields unsub', { inputs });
};
inputs.checkbox?.addEventListener('input', listener);
inputs.number?.addEventListener('input', listener);
inputs.range?.addEventListener('input', listener);
render(style.value);
return [element, style, unsub];
}
let $invert = template(`<fieldset>
<label for="invertVal">Invert Percentage</label>
<input type="range" name="invert" min="${ii}" max="${ia}" value="${i}" />
<input type="number" name="invert" id="invertVal" min="${ii}" max="${ia}" value="${i}" />
</fieldset>`)(),
$hue = template(`<fieldset>
<label for="hueVal">Hue Rotate Degrees</label>
<input type="range" name="hue" min="${hi}" max="${ha}" value="${h}" />
<input type="number" name="hue" id="hueVal" min="${hi}" max="${ha}" value="${h}" />
</fieldset>`)(),
$toggle = template(`<fieldset>
<label>
<input type="checkbox" name="toggle"${toggle ? ' checked' : ''} /> Toggle ${t}
</label>
</fieldset>`)();
setupFields($invert, i);
setupFields($hue, h);
setupFields($toggle, toggle);
s.type="text/css";
s.className="dplgngr";
s.innerText=styles(b, i, h, toggle);
d.body.appendChild(s);
c.className="dplgngr ctrls";
c.innerHTML=`<strong>${t} Controls</strong>`;
c.insertAdjacentElement?.('beforeend', $invert);
c.insertAdjacentElement?.('beforeend', $hue);
c.insertAdjacentElement?.('beforeend', $toggle);
d.body.appendChild(c);
}();
javascript:void function(){ let b='#fff',toggle=true,i=95,ii=0,ia=100,h=180,hi=0,ha=360; const d=document,ce=(n)=>d.createElement(n),q=d.querySelectorAll(".dplgngr"),s=ce("style"),c=ce("form"),t='Doppelgänger'; if(q?.length)return q.forEach(a=>a.remove()); const invertStyles = (b,i,h) => %60html { background-color: ${b}; } html,img,video,iframe { filter: invert(${i}%) hue-rotate(${h}deg); }%60; const styles = (b,i,h,t) => %60${t ? invertStyles(b,i,h) : ''} .dplgngr fieldset { border: 0 solid transparent; margin-top: 1em; padding: 0; display: grid; grid-template-areas: "l l" "r n"; grid-template-columns: 2fr 1fr; column-gap: 1em; } .dplgngr label { grid-area: l; } .dplgngr input { padding: 0; grid-area: r; } .dplgngr input[type="number"] { padding: 2px 4px; grid-area: n; } .dplgngr.ctrls { background: #f9f9f9; color: #111; position: fixed; display: block; z-index: 100000; top: 0; right: 0; bottom: auto; width: 300px; height: auto; padding: 1em; } %60; function createSignal(initialValue) { let _value = initialValue; let _last = _value; const subs = []; function pub() { for (let s of subs) { s && s(_value, _last); } } return { get value() { return _value; }, set value(v) { _last = _value; _value = v; pub(); }, on: s => { const i = subs.push(s)-1; return () => { subs[i] = 0; }; }, }; } function template(html, elementOnly = true) { let node; const create = () => { const t = ce('template'); t.innerHTML = html; return t.content[elementOnly ? 'firstElementChild' : 'firstChild']; }; return () => (node || (node = create())).cloneNode(true); } function setupFields(element, initSignalVal) { let fieldType, style = createSignal(initSignalVal); let inputs = Object.fromEntries([...element.querySelectorAll('input')].map(e => [e.type,e])); const setNumericValue = val => (val >= 0 ? val : 0); const render = (v, p) => { console.log('setupFields render', { fieldType, v, }); if (!fieldType && !inputs.checkbox) { inputs.number.value = v; inputs.range.value = v; } if (fieldType === 'number') { inputs.range.value = v; } if (fieldType === 'range') { inputs.number.value = v; } if (inputs.range?.name === 'hue') { h = v; } if (inputs.range?.name === 'invert') { i = v; } if (fieldType === 'checkbox') { toggle = inputs.checkbox.checked; } s.innerText=styles(b, i, h, toggle); fieldType = undefined; }; const listener = (event) => { fieldType = event.target.type; let v = fieldType === 'checkbox' ? event.target.checked : setNumericValue(event.target.value); console.log('setupFields listener', { fieldType, v, event }); style.value = v; }; const styleOff = style.on(render); const unsub = () => { styleOff(); inputs.checkbox?.removeEventListener('input', listener); inputs.number?.removeEventListener('input', listener); inputs.range?.removeEventListener('input', listener); console.log('setupFields unsub', { inputs }); }; inputs.checkbox?.addEventListener('input', listener); inputs.number?.addEventListener('input', listener); inputs.range?.addEventListener('input', listener); render(style.value); return [element, style, unsub]; } let $invert = template(%60<fieldset> <label for="invertVal">Invert Percentage</label> <input type="range" name="invert" min="${ii}" max="${ia}" value="${i}" /> <input type="number" name="invert" id="invertVal" min="${ii}" max="${ia}" value="${i}" /> </fieldset>%60)(), $hue = template(%60<fieldset> <label for="hueVal">Hue Rotate Degrees</label> <input type="range" name="hue" min="${hi}" max="${ha}" value="${h}" /> <input type="number" name="hue" id="hueVal" min="${hi}" max="${ha}" value="${h}" /> </fieldset>%60)(), $toggle = template(%60<fieldset> <label> <input type="checkbox" name="toggle"${toggle ? ' checked' : ''} /> Toggle ${t} </label> </fieldset>%60)(); setupFields($invert, i); setupFields($hue, h); setupFields($toggle, toggle); s.type="text/css"; s.className="dplgngr"; s.innerText=styles(b, i, h, toggle); d.body.appendChild(s); c.className="dplgngr ctrls"; c.innerHTML=%60<strong>${t} Controls</strong>%60; c.insertAdjacentElement?.('beforeend', $invert); c.insertAdjacentElement?.('beforeend', $hue); c.insertAdjacentElement?.('beforeend', $toggle); d.body.appendChild(c); }();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment