Autoplay in frame video previews plus case insensitive tag blocking
// ==UserScript==
// @name PornXP Enhanced
// @namespace https://github.com/quantavil/
// @version 1.2
// @description Autoplay in frame video previews plus case insensitive tag blocking
// @match *://*.pornxp.*/*
// @match *://*.porn-xp.*/*
// @match *://porn-xp.*/*
// @match *://pornxp.*/*
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @run-at document-end
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'pxp_filters_v1';
const DEFAULTS = { blockedTags: [], mutePreview: true, hideBlocked: true };
let state = loadState();
let blockedSet = new Set();
function loadState() {
try {
return { ...DEFAULTS, ...JSON.parse(localStorage.getItem(STORAGE_KEY)) };
} catch { return { ...DEFAULTS }; }
}
function syncState() {
state.blockedTags = [...new Set(state.blockedTags.map(t => t.toLowerCase()))];
blockedSet = new Set(state.blockedTags);
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
applyFilter();
refreshPanelTagList();
}
const $ = (s, c = document) => c.querySelector(s);
const $$ = (s, c = document) => [...c.querySelectorAll(s)];
const escapeMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
const escapeHtml = str => str.replace(/[&<>"']/g, c => escapeMap[c]);
function parseTagName(href) {
const m = href?.match(/\/tags\/([^/?#]+)/i);
return m ? decodeURIComponent(m[1]).toLowerCase() : null;
}
function getItemTags(cont) {
return $$('.item_tags a', cont).map(a => parseTagName(a.getAttribute('href'))).filter(Boolean);
}
function isBlocked(cont) {
return getItemTags(cont).some(t => blockedSet.has(t));
}
function applyFilter() {
$$('.item_cont').forEach(cont => {
const blocked = isBlocked(cont);
if (state.hideBlocked && blocked) {
cont.style.display = 'none';
cont.style.opacity = '';
cont.style.filter = '';
cont.style.pointerEvents = '';
} else if (!state.hideBlocked && blocked) {
cont.style.display = '';
cont.style.opacity = '0.15';
cont.style.filter = 'blur(4px) grayscale(100%)';
cont.style.pointerEvents = 'none';
} else {
cont.style.display = '';
cont.style.opacity = '';
cont.style.filter = '';
cont.style.pointerEvents = '';
}
});
}
const visObserver = new IntersectionObserver(entries => {
entries.forEach(e => {
const vid = e.target.querySelector('video.pxp-preview');
if (!vid) return;
if (e.isIntersecting) {
vid.style.visibility = 'visible';
vid.style.opacity = '1';
vid.play().catch(() => {});
} else {
vid.pause();
vid.style.opacity = '0';
vid.style.visibility = 'hidden';
}
});
}, { rootMargin: '100px', threshold: 0.2 });
function initPreviews() {
$$('.item.preview').forEach(item => {
if (item.querySelector('video.pxp-preview')) return;
const previewUrl = item.dataset.preview;
const thumb = $('.item_thumb', item);
if (!previewUrl || !thumb) return;
const video = document.createElement('video');
video.className = 'pxp-preview';
video.loop = true;
video.muted = state.mutePreview;
video.playsInline = true;
video.preload = 'none';
video.src = previewUrl;
thumb.appendChild(video);
visObserver.observe(item);
});
$$('video.pxp-preview').forEach(v => v.muted = state.mutePreview);
}
function addBlockButtons() {
$$('.item_tags a').forEach(a => {
if (a.querySelector('.pxp-block-btn')) return;
const tag = parseTagName(a.getAttribute('href'));
if (!tag) return;
const btn = document.createElement('span');
btn.className = 'pxp-block-btn';
btn.innerHTML = '×';
btn.title = `Block "${tag}"`;
btn.onclick = e => {
e.preventDefault();
e.stopPropagation();
if (blockedSet.has(tag)) return;
state.blockedTags.push(tag);
syncState();
};
a.appendChild(btn);
});
}
function refreshPanelTagList() {
const list = $('#pxp-taglist');
if (!list) return;
if (!state.blockedTags.length) {
list.innerHTML = '<div class="pxp-empty">No blocked tags</div>';
return;
}
list.innerHTML = state.blockedTags.map(tag =>
`<div class="pxp-tag"><span>${escapeHtml(tag)}</span><button data-tag="${escapeHtml(tag)}">×</button></div>`
).join('');
list.querySelectorAll('button').forEach(btn => {
btn.onclick = () => {
state.blockedTags = state.blockedTags.filter(t => t !== btn.dataset.tag);
syncState();
};
});
}
function buildPanel() {
const panel = document.createElement('div');
panel.id = 'pxp-settings';
panel.innerHTML = `
<div class="pxp-header"><span>Filters</span><button class="pxp-close">×</button></div>
<div class="pxp-body">
<label class="pxp-row"><input type="checkbox" id="pxp-mute" ${state.mutePreview ? 'checked' : ''}><span>Mute previews</span></label>
<label class="pxp-row"><input type="checkbox" id="pxp-hide" ${state.hideBlocked ? 'checked' : ''}><span>Hide blocked items</span></label>
<div class="pxp-section">Blocked Tags</div>
<div class="pxp-taglist" id="pxp-taglist"></div>
<div class="pxp-addtag">
<input type="text" id="pxp-newtag" placeholder="Tag name">
<button id="pxp-add">Block</button>
</div>
</div>`;
panel.querySelector('.pxp-close').onclick = () => panel.classList.remove('open');
panel.querySelector('#pxp-mute').onchange = e => {
state.mutePreview = e.target.checked;
syncState();
initPreviews();
};
panel.querySelector('#pxp-hide').onchange = e => {
state.hideBlocked = e.target.checked;
syncState();
};
const addTag = () => {
const input = panel.querySelector('#pxp-newtag');
const tag = input.value.trim().toLowerCase();
if (!tag || blockedSet.has(tag)) { input.value = ''; return; }
state.blockedTags.push(tag);
input.value = '';
syncState();
};
panel.querySelector('#pxp-add').onclick = addTag;
panel.querySelector('#pxp-newtag').onkeydown = e => { if (e.key === 'Enter') addTag(); };
refreshPanelTagList();
return panel;
}
GM_addStyle(`
.item_thumb { position: relative !important; overflow: hidden !important; }
.item_thumb img { display: block !important; position: relative !important; z-index: 1 !important; }
video.pxp-preview {
position: absolute !important; top: 0 !important; left: 0 !important;
width: 100% !important; height: 100% !important; object-fit: cover !important;
z-index: 2 !important; opacity: 0; visibility: hidden;
transition: opacity 0.2s ease; pointer-events: none;
}
#pxp-settings {
position:fixed; top:60px; right:-320px; width:300px;
background:#1a1a1a; border:1px solid #333; border-radius:8px;
color:#ddd; font-family:system-ui,sans-serif; font-size:13px;
z-index:99999; transition:right .25s ease; box-shadow:0 8px 32px rgba(0,0,0,.6);
}
#pxp-settings.open { right:12px; }
.pxp-header { display:flex; justify-content:space-between; align-items:center; padding:12px 14px; border-bottom:1px solid #333; font-weight:600; font-size:14px; color:#fff; }
.pxp-close { background:none; border:none; color:#888; font-size:20px; cursor:pointer; line-height:1; }
.pxp-close:hover { color:#fff; }
.pxp-body { padding:12px 14px; }
.pxp-row { display:flex; align-items:center; gap:8px; margin-bottom:10px; cursor:pointer; }
.pxp-row input { cursor:pointer; }
.pxp-section { margin:14px 0 8px; font-weight:600; color:#bbb; text-transform:uppercase; font-size:11px; letter-spacing:.5px; }
.pxp-taglist { max-height:180px; overflow-y:auto; margin-bottom:10px; }
.pxp-empty { color:#666; font-style:italic; padding:8px 0; }
.pxp-tag { display:flex; justify-content:space-between; align-items:center; background:#252525; padding:6px 10px; border-radius:4px; margin-bottom:6px; }
.pxp-tag button { background:none; border:none; color:#e74c3c; cursor:pointer; font-size:16px; }
.pxp-tag button:hover { color:#ff6b6b; }
.pxp-addtag { display:flex; gap:6px; }
.pxp-addtag input { flex:1; background:#252525; border:1px solid #444; color:#ddd; padding:6px 10px; border-radius:4px; font-size:12px; }
.pxp-addtag button { background:#e74c3c; color:#fff; border:none; padding:6px 12px; border-radius:4px; cursor:pointer; font-size:12px; font-weight:500; }
.pxp-addtag button:hover { background:#c0392b; }
#pxp-toggle { position:fixed; top:14px; right:14px; z-index:99999; background:#e74c3c; color:#fff; border:none; padding:8px 14px; border-radius:6px; cursor:pointer; font-weight:600; font-size:13px; box-shadow:0 2px 12px rgba(231,76,60,.3); }
#pxp-toggle:hover { background:#c0392b; }
.item_tags a { position:relative; padding-right:14px!important; }
.pxp-block-btn { display:inline-block; margin-left:4px; color:#e74c3c; cursor:pointer; font-weight:bold; font-size:13px; opacity:0; transition:opacity .15s; }
.item_tags a:hover .pxp-block-btn { opacity:1; }
.pxp-block-btn:hover { color:#ff6b6b; }
`);
function clearCache() {
localStorage.removeItem(STORAGE_KEY);
state = { ...DEFAULTS };
blockedSet = new Set();
syncState();
initPreviews();
}
GM_registerMenuCommand('Clear Cache and Reset Filters', clearCache);
function init() {
const toggle = document.createElement('button');
toggle.id = 'pxp-toggle';
toggle.textContent = 'Filters';
toggle.onclick = () => {
let panel = $('#pxp-settings');
if (!panel) { panel = buildPanel(); document.body.appendChild(panel); }
panel.classList.toggle('open');
};
document.body.appendChild(toggle);
syncState();
initPreviews();
addBlockButtons();
let pending;
const target = $('#content') || document.body;
new MutationObserver(m => {
if (!m.some(r => r.addedNodes.length)) return;
clearTimeout(pending);
pending = setTimeout(() => { initPreviews(); addBlockButtons(); applyFilter(); }, 150);
}).observe(target, { childList: true, subtree: true });
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();