Skip to content

Instantly share code, notes, and snippets.

@playday3008
Created March 14, 2026 18:12
Show Gist options
  • Select an option

  • Save playday3008/23beff9299918bf1af48dbf6e06ce0a3 to your computer and use it in GitHub Desktop.

Select an option

Save playday3008/23beff9299918bf1af48dbf6e06ce0a3 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Domino's Pizza Ingredient Filter
// @namespace https://www.dominospizza.pl
// @match https://www.dominospizza.pl/*
// @grant none
// @version 1.1
// @author PlayDay
// @description Whitelist/blacklist ingredients and filter by tags on Domino's Pizza Poland menu
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'dominos-ingredient-filter';
const TAGS_KEY = 'dominos-tag-filter';
function loadPrefs() {
try {
return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
} catch { return {}; }
}
function loadTagPrefs() {
try {
return JSON.parse(localStorage.getItem(TAGS_KEY) || '{}');
} catch { return {}; }
}
function savePrefs(prefs) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs));
}
function saveTagPrefs(prefs) {
localStorage.setItem(TAGS_KEY, JSON.stringify(prefs));
}
function normalizeIngredient(raw) {
return raw.replace(/\u00a0/g, ' ').trim().toLowerCase();
}
function extractIngredients(descEl) {
return descEl.textContent
.split(',')
.map(normalizeIngredient)
.filter(Boolean);
}
function extractTags(item) {
const tags = [];
item.querySelectorAll('.m-AppProductItem__infoTags-text').forEach(el => {
const t = el.textContent.trim();
if (t) tags.push(t);
});
return tags;
}
function getAllIngredients() {
const set = new Set();
document.querySelectorAll('.m-AppProductItem__infoDescription').forEach(el => {
extractIngredients(el).forEach(i => set.add(i));
});
return [...set].sort((a, b) => a.localeCompare(b, 'pl'));
}
function getAllTags() {
const set = new Set();
document.querySelectorAll('.m-AppProductItem__infoTags-text').forEach(el => {
const t = el.textContent.trim();
if (t) set.add(t);
});
return [...set].sort((a, b) => a.localeCompare(b, 'pl'));
}
const COLORS = { want: '#2e7d32', ban: '#c62828' };
const BGS = { want: '#e8f5e9', ban: '#ffebee' };
function applyFilter(prefs, tagPrefs) {
const items = document.querySelectorAll('.m-AppProductItem');
const hasIngredientWhitelist = Object.values(prefs).includes('want');
const hasTagWhitelist = Object.values(tagPrefs).includes('want');
items.forEach(item => {
const descEl = item.querySelector('.m-AppProductItem__infoDescription');
if (!descEl) return;
const ingredients = extractIngredients(descEl);
const tags = extractTags(item);
// Tag filter — "want" tags override ingredient filter entirely
const hasWantedTag = tags.some(t => tagPrefs[t] === 'want');
const hasBannedTag = tags.some(t => tagPrefs[t] === 'ban');
let hidden = false;
if (hasWantedTag) {
// Wanted tag = always show, skip ingredient logic
hidden = false;
} else if (hasBannedTag) {
hidden = true;
} else {
// Ingredient-level filtering
const hasBannedIng = ingredients.some(i => prefs[i] === 'ban');
const hasWantedIng = ingredients.some(i => prefs[i] === 'want');
if (hasBannedIng) hidden = true;
if (hasIngredientWhitelist && !hasWantedIng) hidden = true;
// If tag whitelist is active but this pizza has no wanted tag, dim it too
if (hasTagWhitelist) hidden = true;
}
item.style.opacity = hidden ? '0.15' : '1';
item.style.order = hidden ? '999' : '';
// Highlight matching ingredients in description
const parts = descEl.textContent.split(',').map(s => s.replace(/\u00a0/g, ' ').trim());
descEl.innerHTML = parts.map(p => {
const norm = p.toLowerCase();
const state = prefs[norm] || null;
if (state === 'want') return `<span style="color:#2e7d32;font-weight:bold">${p}</span>`;
if (state === 'ban') return `<span style="color:#c62828;text-decoration:line-through">${p}</span>`;
return p;
}).join(', ');
});
}
function createPanel() {
const prefs = loadPrefs();
const tagPrefs = loadTagPrefs();
const ingredients = getAllIngredients();
const tags = getAllTags();
const panel = document.createElement('div');
panel.id = 'dominos-filter-panel';
Object.assign(panel.style, {
position: 'fixed', top: '10px', right: '10px', zIndex: '999999',
background: '#fff', border: '2px solid #0b6ab0', borderRadius: '10px',
padding: '0', maxHeight: '90vh', width: '320px',
boxShadow: '0 4px 20px rgba(0,0,0,0.3)', fontFamily: 'Arial, sans-serif',
display: 'flex', flexDirection: 'column', overflow: 'hidden'
});
// Header
const header = document.createElement('div');
Object.assign(header.style, {
padding: '10px 14px', background: '#0b6ab0', color: '#fff',
fontWeight: 'bold', fontSize: '14px', cursor: 'pointer',
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
userSelect: 'none'
});
header.innerHTML = '<span>Pizza Filter</span><span id="df-toggle">_</span>';
panel.appendChild(header);
// Body
const body = document.createElement('div');
body.id = 'df-body';
Object.assign(body.style, {
padding: '10px 14px', overflowY: 'auto', flex: '1'
});
// Reset button
const resetBtn = document.createElement('button');
resetBtn.textContent = 'Reset All';
Object.assign(resetBtn.style, {
width: '100%', padding: '6px', marginBottom: '10px', cursor: 'pointer',
border: '1px solid #ccc', borderRadius: '4px', background: '#f5f5f5',
fontSize: '12px'
});
resetBtn.addEventListener('click', () => {
savePrefs({});
saveTagPrefs({});
panel.remove();
createPanel();
});
body.appendChild(resetBtn);
function makeToggleBtn(key, state, text, color, bg, currentPrefs, saveFn) {
const btn = document.createElement('button');
btn.textContent = text;
const isActive = currentPrefs[key] === state;
Object.assign(btn.style, {
padding: '2px 8px', fontSize: '11px', cursor: 'pointer',
border: `1px solid ${color}`, borderRadius: '3px',
background: isActive ? bg : '#fff',
color: isActive ? color : '#aaa',
fontWeight: isActive ? 'bold' : 'normal'
});
btn.dataset.state = state;
btn.addEventListener('click', () => {
const current = saveFn === savePrefs ? loadPrefs() : loadTagPrefs();
if (current[key] === state) {
delete current[key];
} else {
current[key] = state;
}
saveFn(current);
applyFilter(
saveFn === savePrefs ? current : loadPrefs(),
saveFn === saveTagPrefs ? current : loadTagPrefs()
);
// Update sibling buttons
btn.parentElement.querySelectorAll('button').forEach(b => {
const bActive = current[key] === b.dataset.state;
const bColor = b.dataset.state === 'want' ? COLORS.want : COLORS.ban;
const bBg = b.dataset.state === 'want' ? BGS.want : BGS.ban;
b.style.background = bActive ? bBg : '#fff';
b.style.color = bActive ? bColor : '#aaa';
b.style.fontWeight = bActive ? 'bold' : 'normal';
});
});
return btn;
}
function makeRow(key, label, currentPrefs, saveFn) {
const row = document.createElement('div');
Object.assign(row.style, {
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '4px 0', borderBottom: '1px solid #eee'
});
const labelEl = document.createElement('span');
labelEl.textContent = label;
Object.assign(labelEl.style, {
fontSize: '13px', flex: '1', marginRight: '8px'
});
const btnGroup = document.createElement('div');
Object.assign(btnGroup.style, { display: 'flex', gap: '4px' });
btnGroup.appendChild(makeToggleBtn(key, 'want', 'Want', '#2e7d32', '#e8f5e9', currentPrefs, saveFn));
btnGroup.appendChild(makeToggleBtn(key, 'ban', 'Ban', '#c62828', '#ffebee', currentPrefs, saveFn));
row.appendChild(labelEl);
row.appendChild(btnGroup);
return row;
}
function makeSectionHeader(text) {
const h = document.createElement('div');
Object.assign(h.style, {
fontSize: '12px', fontWeight: 'bold', color: '#0b6ab0',
padding: '8px 0 4px', borderBottom: '2px solid #0b6ab0',
marginTop: '6px', textTransform: 'uppercase', letterSpacing: '0.5px'
});
h.textContent = text;
return h;
}
// Tags section
if (tags.length > 0) {
body.appendChild(makeSectionHeader('Tags'));
tags.forEach(tag => {
body.appendChild(makeRow(tag, tag, tagPrefs, saveTagPrefs));
});
}
// Ingredients section
body.appendChild(makeSectionHeader('Ingredients'));
ingredients.forEach(ing => {
const row = makeRow(ing, ing, prefs, savePrefs);
row.querySelector('span').style.textTransform = 'capitalize';
body.appendChild(row);
});
panel.appendChild(body);
// Legend
const legend = document.createElement('div');
Object.assign(legend.style, {
padding: '8px 14px', background: '#f5f5f5', fontSize: '11px',
color: '#666', borderTop: '1px solid #ddd'
});
legend.innerHTML = '<b>Want</b> tag overrides ingredient filter<br><b>Ban</b>: hide pizzas &nbsp;|&nbsp; <b>Want</b>: show only matching';
panel.appendChild(legend);
// Toggle collapse
let collapsed = false;
header.addEventListener('click', () => {
collapsed = !collapsed;
body.style.display = collapsed ? 'none' : 'block';
legend.style.display = collapsed ? 'none' : 'block';
const toggle = document.getElementById('df-toggle');
if (toggle) toggle.textContent = collapsed ? '+' : '_';
});
document.body.appendChild(panel);
applyFilter(prefs, tagPrefs);
}
// Wait for menu to load, then init
function init() {
if (document.querySelector('.m-AppProductItem__infoDescription')) {
createPanel();
} else {
const observer = new MutationObserver(() => {
if (document.querySelector('.m-AppProductItem__infoDescription')) {
observer.disconnect();
createPanel();
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment