add option to blur any thumb

This commit is contained in:
2026-05-23 15:26:35 +02:00
parent c488b93290
commit 4b50e56eb8
8 changed files with 358 additions and 2 deletions

View File

@@ -2328,6 +2328,11 @@ body.layout-modern .item-sidebar-left .tag-controls {
cursor: wait;
}
.submit-comment.loading .fa-spinner {
font-size: 11px;
vertical-align: middle;
}
.submit-comment.uploading {
pointer-events: none;
opacity: 0.5;
@@ -14560,4 +14565,144 @@ body.scroller-active #gchat-reopen-bubble {
.info-table tr:last-child th,
.info-table tr:last-child td {
border-bottom: none;
}
/* =============================================
NSFW / NSFL PREMIUM ZERO-LAG BACKGROUND BLUR
============================================= */
/* Stretch the thumbnail paragraph to cover the whole box */
.blur-nsfw-active div.posts > a.thumb[data-mode="nsfw"] p,
.blur-nsfl-active div.posts > a.thumb[data-mode="nsfl"] p,
.blur-sfw-active div.posts > a.thumb[data-mode="sfw"] p,
.blur-untagged-active div.posts > a.thumb[data-mode="null"] p {
position: absolute !important;
inset: 0 !important;
margin: 0 !important;
padding: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
z-index: 8 !important;
}
/* Premium Blur Overlay for NSFW */
.blur-nsfw-active div.posts > a.thumb[data-mode="nsfw"] p::after {
content: "NSFW" !important;
position: absolute !important;
inset: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
color: #ffffff !important;
font-weight: 800 !important;
font-size: 14px !important;
letter-spacing: 2px !important;
text-shadow: 0 2px 5px rgba(0, 0, 0, 0.9) !important;
background: rgba(15, 15, 20, 0.5) !important;
pointer-events: none !important;
opacity: 1 !important;
transition: opacity 0.35s cubic-bezier(0.25, 1, 0.5, 1) !important;
}
/* Premium Blur Overlay for NSFL */
.blur-nsfl-active div.posts > a.thumb[data-mode="nsfl"] p::after {
content: "NSFL" !important;
position: absolute !important;
inset: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
color: #ff3b30 !important;
font-weight: 800 !important;
font-size: 14px !important;
letter-spacing: 2px !important;
text-shadow: 0 2px 5px rgba(0, 0, 0, 0.9) !important;
background: rgba(15, 15, 20, 0.6) !important;
pointer-events: none !important;
opacity: 1 !important;
transition: opacity 0.35s cubic-bezier(0.25, 1, 0.5, 1) !important;
}
/* Premium Blur Overlay for SFW */
.blur-sfw-active div.posts > a.thumb[data-mode="sfw"] p::after {
content: "SFW" !important;
position: absolute !important;
inset: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
color: #30d158 !important;
font-weight: 800 !important;
font-size: 14px !important;
letter-spacing: 2px !important;
text-shadow: 0 2px 5px rgba(0, 0, 0, 0.9) !important;
background: rgba(15, 15, 20, 0.45) !important;
backdrop-filter: blur(15px) !important;
-webkit-backdrop-filter: blur(15px) !important;
pointer-events: none !important;
opacity: 1 !important;
transition: opacity 0.35s cubic-bezier(0.25, 1, 0.5, 1) !important;
}
/* Premium Blur Overlay for Untagged */
.blur-untagged-active div.posts > a.thumb[data-mode="null"] p::after {
content: "UNTAGGED" !important;
position: absolute !important;
inset: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
color: #ff9f0a !important;
font-weight: 800 !important;
font-size: 14px !important;
letter-spacing: 2px !important;
text-shadow: 0 2px 5px rgba(0, 0, 0, 0.9) !important;
background: rgba(15, 15, 20, 0.5) !important;
backdrop-filter: blur(18px) !important;
-webkit-backdrop-filter: blur(18px) !important;
pointer-events: none !important;
opacity: 1 !important;
transition: opacity 0.35s cubic-bezier(0.25, 1, 0.5, 1) !important;
}
/* Click reveal transition state (reveals on click adding .revealed, no hover reveal) */
.blur-nsfw-active div.posts > a.thumb.revealed p::after,
.blur-nsfl-active div.posts > a.thumb.revealed p::after,
.blur-sfw-active div.posts > a.thumb.revealed p::after,
.blur-untagged-active div.posts > a.thumb.revealed p::after {
opacity: 0 !important;
}
/* Premium corner hide button styled dynamically inside revealed cards */
.blur-nsfw-active a.thumb p .hide-thumb-btn,
.blur-nsfl-active a.thumb p .hide-thumb-btn,
.blur-sfw-active a.thumb p .hide-thumb-btn,
.blur-untagged-active a.thumb p .hide-thumb-btn {
display: none !important;
}
.blur-nsfw-active a.thumb.revealed p .hide-thumb-btn,
.blur-nsfl-active a.thumb.revealed p .hide-thumb-btn,
.blur-sfw-active a.thumb.revealed p .hide-thumb-btn,
.blur-untagged-active a.thumb.revealed p .hide-thumb-btn {
display: flex !important;
position: absolute !important;
bottom: 5px !important;
left: 5px !important;
width: 20px !important;
height: 20px !important;
border-radius: 50% !important;
background: rgba(0, 0, 0, 0.75) !important;
border: 1px solid rgba(255, 255, 255, 0.2) !important;
color: #ffffff !important;
align-items: center !important;
justify-content: center !important;
cursor: pointer !important;
z-index: 100 !important;
pointer-events: auto !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5) !important;
transition: background 0.2s, transform 0.2s, border-color 0.2s, color 0.2s !important;
padding: 0 !important;
font-size: 11px !important;
line-height: 1 !important;
}

View File

@@ -40,6 +40,11 @@ window.cancelAnimFrame = (function () {
if (ua.includes('Chrome')) htmlEl.classList.add('is-chrome');
if (ua.includes('Safari') && !ua.includes('Chrome')) htmlEl.classList.add('is-safari');
if (localStorage.getItem('blurNsfw') === 'true') htmlEl.classList.add('blur-nsfw-active');
if (localStorage.getItem('blurNsfl') === 'true') htmlEl.classList.add('blur-nsfl-active');
if (localStorage.getItem('blurSfw') === 'true') htmlEl.classList.add('blur-sfw-active');
if (localStorage.getItem('blurUntagged') === 'true') htmlEl.classList.add('blur-untagged-active');
window.updateVisitIndicators = () => {
try {
// View indicators and counters have been permanently removed as requested.
@@ -138,8 +143,17 @@ window.cancelAnimFrame = (function () {
window.initLazyLoading = () => {
if (!('IntersectionObserver' in window)) {
document.querySelectorAll('.lazy-thumb').forEach(thumb => {
if (thumb.dataset.bg) {
const finalBg = window.applyThumbCacheBust(thumb.dataset.bg);
let bg = thumb.dataset.bg;
if (bg) {
const mode = thumb.getAttribute('data-mode');
const blurNsfw = localStorage.getItem('blurNsfw') === 'true';
const blurNsfl = localStorage.getItem('blurNsfl') === 'true';
if (mode === 'nsfw' && blurNsfw && !thumb.classList.contains('revealed')) {
bg = bg.replace('.webp', '_blur.webp');
} else if (mode === 'nsfl' && blurNsfl && !thumb.classList.contains('revealed')) {
bg = bg.replace('.webp', '_blur.webp');
}
const finalBg = window.applyThumbCacheBust(bg);
thumb.style.backgroundImage = `url('${finalBg}')`;
thumb.classList.remove('lazy-thumb');
}
@@ -154,6 +168,15 @@ window.cancelAnimFrame = (function () {
const thumb = entry.target;
let bg = thumb.dataset.bg;
if (bg && !thumb.classList.contains('loaded')) {
const mode = thumb.getAttribute('data-mode');
const blurNsfw = localStorage.getItem('blurNsfw') === 'true';
const blurNsfl = localStorage.getItem('blurNsfl') === 'true';
if (mode === 'nsfw' && blurNsfw && !thumb.classList.contains('revealed')) {
bg = bg.replace('.webp', '_blur.webp');
} else if (mode === 'nsfl' && blurNsfl && !thumb.classList.contains('revealed')) {
bg = bg.replace('.webp', '_blur.webp');
}
bg = window.applyThumbCacheBust(bg);
const img = new Image();
img.onload = () => {
@@ -8735,4 +8758,71 @@ if (navigator.vibrate) {
if (window.resetGlobalScrollState) window.resetGlobalScrollState();
if (window.hideAllModals) window.hideAllModals();
});
// Global click interceptor for dynamically blurred thumbnails (NSFW, NSFL, SFW, Untagged)
document.addEventListener('click', (e) => {
// If no blur options are active, skip entirely
const blurNsfw = localStorage.getItem('blurNsfw') === 'true';
const blurNsfl = localStorage.getItem('blurNsfl') === 'true';
const blurSfw = localStorage.getItem('blurSfw') === 'true';
const blurUntagged = localStorage.getItem('blurUntagged') === 'true';
if (!blurNsfw && !blurNsfl && !blurSfw && !blurUntagged) return;
// Check if they clicked the elegant hide-again button in the corner
const hideBtn = e.target.closest('.hide-thumb-btn');
if (hideBtn) {
e.preventDefault();
e.stopPropagation();
const thumb = hideBtn.closest('a.thumb');
if (thumb) {
thumb.classList.remove('revealed');
hideBtn.remove();
// Put the pre-blurred background image back if applicable
const baseBg = thumb.dataset.bg;
const mode = thumb.getAttribute('data-mode');
if (baseBg && (mode === 'nsfw' || mode === 'nsfl')) {
const finalBg = window.applyThumbCacheBust(baseBg.replace('.webp', '_blur.webp'));
thumb.style.backgroundImage = `url('${finalBg}')`;
}
}
return;
}
const thumb = e.target.closest('a.thumb');
if (!thumb) return;
// Determine if this thumbnail rating is set to be blurred
const mode = thumb.getAttribute('data-mode'); // 'nsfw', 'nsfl', 'sfw', or 'null' (untagged)
let shouldBlurThis = false;
if (mode === 'nsfw') shouldBlurThis = blurNsfw;
else if (mode === 'nsfl') shouldBlurThis = blurNsfl;
else if (mode === 'sfw') shouldBlurThis = blurSfw;
else if (mode === 'null' || !mode) shouldBlurThis = blurUntagged;
if (shouldBlurThis) {
if (!thumb.classList.contains('revealed')) {
e.preventDefault();
e.stopPropagation();
thumb.classList.add('revealed');
// Dynamically load the standard unblurred background image
const baseBg = thumb.dataset.bg;
if (baseBg && (mode === 'nsfw' || mode === 'nsfl')) {
const finalBg = window.applyThumbCacheBust(baseBg);
thumb.style.backgroundImage = `url('${finalBg}')`;
}
// Dynamically inject the hide-again eye-slash button inside <p>
const p = thumb.querySelector('p');
if (p && !p.querySelector('.hide-thumb-btn')) {
const btn = document.createElement('button');
btn.className = 'hide-thumb-btn';
btn.innerHTML = '<i class="fa-solid fa-eye-slash"></i>';
btn.title = 'Hide thumbnail again';
p.appendChild(btn);
}
}
}
}, true);
})();

View File

@@ -719,6 +719,67 @@
});
}
// Granular Thumbnail Blur Toggles
const blurNsfwToggle = document.getElementById('blur_nsfw_toggle');
if (blurNsfwToggle) {
blurNsfwToggle.checked = localStorage.getItem('blurNsfw') === 'true';
blurNsfwToggle.addEventListener('change', () => {
const enabled = blurNsfwToggle.checked;
localStorage.setItem('blurNsfw', enabled ? 'true' : 'false');
if (enabled) {
document.documentElement.classList.add('blur-nsfw-active');
} else {
document.documentElement.classList.remove('blur-nsfw-active');
}
showStatus(enabled ? 'NSFW blurring enabled!' : 'NSFW blurring disabled!', 'success');
});
}
const blurNsflToggle = document.getElementById('blur_nsfl_toggle');
if (blurNsflToggle) {
blurNsflToggle.checked = localStorage.getItem('blurNsfl') === 'true';
blurNsflToggle.addEventListener('change', () => {
const enabled = blurNsflToggle.checked;
localStorage.setItem('blurNsfl', enabled ? 'true' : 'false');
if (enabled) {
document.documentElement.classList.add('blur-nsfl-active');
} else {
document.documentElement.classList.remove('blur-nsfl-active');
}
showStatus(enabled ? 'NSFL blurring enabled!' : 'NSFL blurring disabled!', 'success');
});
}
const blurSfwToggle = document.getElementById('blur_sfw_toggle');
if (blurSfwToggle) {
blurSfwToggle.checked = localStorage.getItem('blurSfw') === 'true';
blurSfwToggle.addEventListener('change', () => {
const enabled = blurSfwToggle.checked;
localStorage.setItem('blurSfw', enabled ? 'true' : 'false');
if (enabled) {
document.documentElement.classList.add('blur-sfw-active');
} else {
document.documentElement.classList.remove('blur-sfw-active');
}
showStatus(enabled ? 'SFW blurring enabled!' : 'SFW blurring disabled!', 'success');
});
}
const blurUntaggedToggle = document.getElementById('blur_untagged_toggle');
if (blurUntaggedToggle) {
blurUntaggedToggle.checked = localStorage.getItem('blurUntagged') === 'true';
blurUntaggedToggle.addEventListener('change', () => {
const enabled = blurUntaggedToggle.checked;
localStorage.setItem('blurUntagged', enabled ? 'true' : 'false');
if (enabled) {
document.documentElement.classList.add('blur-untagged-active');
} else {
document.documentElement.classList.remove('blur-untagged-active');
}
showStatus(enabled ? 'Untagged blurring enabled!' : 'Untagged blurring disabled!', 'success');
});
}
// Background Blur Toggle
const backgroundToggle = document.getElementById('show_background_toggle');
if (backgroundToggle) {

View File

@@ -148,6 +148,14 @@
"image_expand_on_click_hint": "Anstatt das Scroll-Zoom-Modal zu öffnen, wird ein Bild beim Klicken innerhalb der Seite auf volle Größe erweitert.",
"enable_bg_blur": "Hintergrundunschärfe aktivieren",
"enable_bg_blur_hint": "Unscharfen Hintergrund bei Beiträgen anzeigen",
"blur_nsfw": "NSFW weichzeichnen",
"blur_nsfw_hint": "Zeichnet NSFW-Vorschaubilder weich.",
"blur_nsfl": "NSFL weichzeichnen",
"blur_nsfl_hint": "Zeichnet NSFL-Vorschaubilder weich.",
"blur_sfw": "SFW weichzeichnen",
"blur_sfw_hint": "Zeichnet SFW-Vorschaubilder weich.",
"blur_untagged": "Unmarkierte weichzeichnen",
"blur_untagged_hint": "Zeichnet unmarkierte Vorschaubilder weich. Klicken zum Aufdecken, Klick auf das durchgestrichene Auge zum erneuten Falten.",
"render_emojis": "Emojis in Zitatantworten anzeigen",
"render_emojis_hint": ":emoji:-Bilder in >zitierten Zeilen anzeigen",
"embed_yt": "YouTube-Videos in Kommentaren einbetten",

View File

@@ -148,6 +148,14 @@
"image_expand_on_click_hint": "Instead of opening the scroll zoom modal, clicking an image will expand it to full size within the page.",
"enable_bg_blur": "Enable Background blur",
"enable_bg_blur_hint": "Show blurred background on items",
"blur_nsfw": "Blur NSFW",
"blur_nsfw_hint": "Blur NSFW-rated thumbnails.",
"blur_nsfl": "Blur NSFL",
"blur_nsfl_hint": "Blur NSFL-rated/shock thumbnails.",
"blur_sfw": "Blur SFW",
"blur_sfw_hint": "Blur SFW-rated thumbnails.",
"blur_untagged": "Blur Untagged",
"blur_untagged_hint": "Blur thumbnails with no tags or rating. Click to reveal, and click the eye-slash to hide again.",
"render_emojis": "Render emojis in quote replies",
"render_emojis_hint": "Show :emoji: images inside >quoted lines",
"embed_yt": "Embed YouTube links in comments",

View File

@@ -148,6 +148,14 @@
"image_expand_on_click_hint": "In plaats van de scroll-zoom-modal te openen, wordt een afbeelding bij klikken vergroot tot volledige grootte binnen de pagina.",
"enable_bg_blur": "Achtergrondvervaging inschakelen",
"enable_bg_blur_hint": "Vervaagde achtergrond tonen bij items",
"blur_nsfw": "NSFW vervagen",
"blur_nsfw_hint": "Vervaagt NSFW-thumbnails.",
"blur_nsfl": "NSFL vervagen",
"blur_nsfl_hint": "Vervaagt NSFL-thumbnails.",
"blur_sfw": "SFW vervagen",
"blur_sfw_hint": "Vervaagt SFW-thumbnails.",
"blur_untagged": "Ongetagde vervagen",
"blur_untagged_hint": "Vervaagt ongetagde thumbnails. Klik om te tonen, klik op het doorgestreepte oog om opnieuw te verbergen.",
"render_emojis": "Emoji's weergeven in antwoorden",
"render_emojis_hint": "Toon :emoji: afbeeldingen binnen >geciteerde regels",
"embed_yt": "YouTube-links insluiten in opmerkingen",

View File

@@ -148,6 +148,14 @@
"image_expand_on_click_hint": "Anstatt dat Scroll-Zoom-Moped aufzumache, wird n Bild beim Klickle uff volle Größ im Bereich uffgepumpt.",
"enable_bg_blur": "Hintergrundunschärfe aktivieren",
"enable_bg_blur_hint": "Gefalteten Hintergrund auf Elementen anzeigen",
"blur_nsfw": "NSFW falten",
"blur_nsfw_hint": "Faltet NSFW-Vorschaubilder.",
"blur_nsfl": "NSFL falten",
"blur_nsfl_hint": "Faltet NSFL-Vorschaubilder.",
"blur_sfw": "SFW falten",
"blur_sfw_hint": "Faltet SFW-Vorschaubilder.",
"blur_untagged": "Unmarkierte falten",
"blur_untagged_hint": "Faltet unmarkierte Vorschaubilder.",
"render_emojis": "Emojis in Zitatantworten darstellen",
"render_emojis_hint": ":emoji:-Bilder innerhalb von >zitierten Zeilen anzeigen",
"embed_yt": "Röhrenelfen in Kommentaren einbetten",

View File

@@ -139,6 +139,34 @@
</label>
<small class="text-muted" style="margin-left: 25px;">{{ t('settings.enable_bg_blur_hint') }}</small>
</div>
<div class="setting-item" style="margin-top: 15px;">
<label for="blur_nsfw_toggle" style="cursor: pointer; display: flex; align-items: center; gap: 10px;">
<input type="checkbox" id="blur_nsfw_toggle">
<span>{{ t('settings.blur_nsfw') }}</span>
</label>
<small class="text-muted" style="margin-left: 25px;">{{ t('settings.blur_nsfw_hint') }}</small>
</div>
<div class="setting-item" style="margin-top: 15px;">
<label for="blur_nsfl_toggle" style="cursor: pointer; display: flex; align-items: center; gap: 10px;">
<input type="checkbox" id="blur_nsfl_toggle">
<span>{{ t('settings.blur_nsfl') }}</span>
</label>
<small class="text-muted" style="margin-left: 25px;">{{ t('settings.blur_nsfl_hint') }}</small>
</div>
<div class="setting-item" style="margin-top: 15px;">
<label for="blur_sfw_toggle" style="cursor: pointer; display: flex; align-items: center; gap: 10px;">
<input type="checkbox" id="blur_sfw_toggle">
<span>{{ t('settings.blur_sfw') }}</span>
</label>
<small class="text-muted" style="margin-left: 25px;">{{ t('settings.blur_sfw_hint') }}</small>
</div>
<div class="setting-item" style="margin-top: 15px;">
<label for="blur_untagged_toggle" style="cursor: pointer; display: flex; align-items: center; gap: 10px;">
<input type="checkbox" id="blur_untagged_toggle">
<span>{{ t('settings.blur_untagged') }}</span>
</label>
<small class="text-muted" style="margin-left: 25px;">{{ t('settings.blur_untagged_hint') }}</small>
</div>
<div class="setting-item" style="margin-top: 15px;">
<label for="quote_emojis_toggle" style="cursor: pointer; display: flex; align-items: center; gap: 10px;">
<input type="checkbox" id="quote_emojis_toggle" @if(session.quote_emojis !==false) checked @endif>