This commit is contained in:
2026-06-04 20:55:41 +02:00
parent 4943a87e13
commit f36c10b428
3 changed files with 109 additions and 82 deletions

View File

@@ -15688,93 +15688,48 @@ body.scroller-active #gchat-reopen-bubble {
/* =============================================
NOTIFICATION THUMBNAIL BLUR
Blur is driven by the current mode filter set
on <html data-notif-filter="sfw|nsfw|nsfl|all">.
No extra settings required.
JS swaps img src to /t/{id}_blur.webp and adds
.notif-thumb-blurred. CSS only handles the label
overlay and hover/reveal transitions.
============================================= */
/* SFW mode: blur NSFW and NSFL thumbnails */
html[data-notif-filter="sfw"] .notif-thumb[data-mode="nsfw"] img,
html[data-notif-filter="sfw"] .notif-thumb[data-mode="nsfl"] img {
filter: blur(8px) !important;
transform: scale(1.08) !important;
transition: filter 0.2s ease, transform 0.2s ease !important;
/* Blurred thumb: position for overlay */
.notif-thumb.notif-thumb-blurred {
position: relative;
}
/* NSFW mode: blur NSFL thumbnails */
html[data-notif-filter="nsfw"] .notif-thumb[data-mode="nsfl"] img {
filter: blur(8px) !important;
transform: scale(1.08) !important;
transition: filter 0.2s ease, transform 0.2s ease !important;
/* Overlay label */
.notif-thumb.notif-thumb-blurred::after {
content: attr(data-mode);
text-transform: uppercase;
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 10px;
letter-spacing: 1.5px;
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
background: rgba(10, 10, 12, 0.45);
color: #ffffff;
pointer-events: none;
transition: opacity 0.15s ease;
z-index: 2;
}
/* NSFL mode: blur SFW and NSFW thumbnails */
html[data-notif-filter="nsfl"] .notif-thumb[data-mode="sfw"] img,
html[data-notif-filter="nsfl"] .notif-thumb[data-mode="nsfw"] img {
filter: blur(8px) !important;
transform: scale(1.08) !important;
transition: filter 0.2s ease, transform 0.2s ease !important;
/* NSFL label gets a red tint */
.notif-thumb.notif-thumb-blurred[data-mode="nsfl"]::after {
color: #ff3b30;
}
/* Overlay label — SFW mode */
html[data-notif-filter="sfw"] .notif-thumb[data-mode="nsfw"],
html[data-notif-filter="sfw"] .notif-thumb[data-mode="nsfl"],
html[data-notif-filter="nsfw"] .notif-thumb[data-mode="nsfl"],
html[data-notif-filter="nsfl"] .notif-thumb[data-mode="sfw"],
html[data-notif-filter="nsfl"] .notif-thumb[data-mode="nsfw"] {
position: relative !important;
/* Hide overlay on hover (JS handles the actual src swap) */
.notif-thumb.notif-thumb-blurred:hover::after {
opacity: 0;
}
html[data-notif-filter="sfw"] .notif-thumb[data-mode="nsfw"]::after,
html[data-notif-filter="sfw"] .notif-thumb[data-mode="nsfl"]::after,
html[data-notif-filter="nsfw"] .notif-thumb[data-mode="nsfl"]::after,
html[data-notif-filter="nsfl"] .notif-thumb[data-mode="sfw"]::after,
html[data-notif-filter="nsfl"] .notif-thumb[data-mode="nsfw"]::after {
content: attr(data-mode) !important;
text-transform: uppercase !important;
position: absolute !important;
inset: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
font-weight: 800 !important;
font-size: 10px !important;
letter-spacing: 1.5px !important;
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9) !important;
background: rgba(10, 10, 12, 0.55) !important;
color: #ffffff !important;
pointer-events: none !important;
opacity: 1 !important;
transition: opacity 0.2s ease !important;
z-index: 2 !important;
}
html[data-notif-filter="sfw"] .notif-thumb[data-mode="nsfl"]::after,
html[data-notif-filter="nsfw"] .notif-thumb[data-mode="nsfl"]::after { color: #ff3b30 !important; }
/* Reveal on hover — only when hovering directly on the thumb */
.notif-thumb[data-mode]:hover img {
filter: none !important;
transform: scale(1.1) !important;
}
.notif-thumb[data-mode]:hover::after {
opacity: 0 !important;
}
/* Revealed state (set by JS tap-to-reveal for mobile) */
.notif-thumb[data-mode].revealed img {
filter: none !important;
}
.notif-thumb[data-mode].revealed::after {
opacity: 0 !important;
pointer-events: none !important;
}
/* ALL mode: no blur on notification thumbnails */
html.mode-all .notif-thumb[data-mode] img {
filter: none !important;
transform: none !important;
}
html.mode-all .notif-thumb[data-mode]::after {
display: none !important;
/* Revealed state: hide overlay */
.notif-thumb.notif-thumb-blurred.revealed::after {
opacity: 0;
pointer-events: none;
}

View File

@@ -505,19 +505,64 @@ window.cancelAnimFrame = (function () {
}
// Track active mode on <html> for notification thumbnail blur.
// data-notif-filter is set to the current mode string so CSS can blur
// thumbnails whose rating doesn't match the active filter.
// data-notif-filter is set to the current mode string and also
// drives src-swapping to pre-blurred thumbnails (/t/{id}_blur.webp).
const _notifBlurShouldBlur = (itemMode, activeMode) => {
if (activeMode === 3) return false; // ALL mode: never blur
if (activeMode === 0) return itemMode === 'nsfw' || itemMode === 'nsfl'; // SFW filter
if (activeMode === 1) return itemMode === 'nsfl'; // NSFW filter
if (activeMode === 4) return itemMode === 'sfw' || itemMode === 'nsfw'; // NSFL filter
return false;
};
window.applyNotifThumbBlur = (container) => {
const root = container || document;
const mode = window.activeMode ?? 3;
root.querySelectorAll('.notif-thumb[data-mode]').forEach(thumb => {
const img = thumb.querySelector('img');
if (!img) return;
const itemMode = thumb.dataset.mode;
if (!itemMode || thumb.classList.contains('revealed')) return;
if (_notifBlurShouldBlur(itemMode, mode)) {
if (!img.dataset.origSrc) {
// Extract numeric item ID from any thumbnail path variant:
// /t/{id}.webp, /mod/pending/t/{id}.webp, /mod/deleted/t/{id}.webp
const idMatch = img.src.match(/\/t\/(\d+)\.webp/);
if (idMatch) {
// Always use the canonical /t/{id}.webp as the sharp src (hover target)
img.dataset.origSrc = `/t/${idMatch[1]}.webp`;
img.src = `/t/${idMatch[1]}_blur.webp`;
thumb.classList.add('notif-thumb-blurred');
}
}
} else {
// Restore original thumbnail
if (img.dataset.origSrc) {
img.src = img.dataset.origSrc;
delete img.dataset.origSrc;
thumb.classList.remove('notif-thumb-blurred');
}
}
});
};
const _updateNotifFilterClass = (mode) => {
const modeNames = { 0: 'sfw', 1: 'nsfw', 2: 'untagged', 3: 'all', 4: 'nsfl' };
htmlEl.setAttribute('data-notif-filter', modeNames[mode] || 'all');
// Keep mode-all class in sync (used by existing CSS override)
if (mode === 3) htmlEl.classList.add('mode-all');
else htmlEl.classList.remove('mode-all');
// Re-apply blur src swaps whenever mode changes
window.applyNotifThumbBlur();
};
_updateNotifFilterClass(window.activeMode ?? 3);
document.addEventListener('f0ck:modeChanged', (e) => {
_updateNotifFilterClass(e.detail?.mode ?? 3);
});
// Apply to server-rendered /notifications page on load
document.addEventListener('DOMContentLoaded', () => window.applyNotifThumbBlur());
// ---- Multi-select Rating Toggles ----
// Reads/writes a `ratings` cookie (e.g. "sfw|untagged") and syncs with server via /mode/3 (ALL).
@@ -6596,6 +6641,12 @@ class NotificationSystem {
e.preventDefault();
e.stopPropagation();
tappedThumb.classList.add('revealed');
tappedThumb.classList.remove('notif-thumb-blurred');
const img = tappedThumb.querySelector('img');
if (img && img.dataset.origSrc) {
img.src = img.dataset.origSrc;
delete img.dataset.origSrc;
}
return;
}
}
@@ -6716,6 +6767,25 @@ class NotificationSystem {
}
});
// Hover src-swap: show sharp thumbnail on hover, restore blur on mouseout
const _notifThumbHoverIn = (e) => {
const thumb = e.target.closest('.notif-thumb.notif-thumb-blurred');
if (!thumb || thumb.classList.contains('revealed')) return;
const img = thumb.querySelector('img');
if (img && img.dataset.origSrc) img.src = img.dataset.origSrc;
};
const _notifThumbHoverOut = (e) => {
const thumb = e.target.closest('.notif-thumb.notif-thumb-blurred');
if (!thumb || thumb.classList.contains('revealed')) return;
const img = thumb.querySelector('img');
if (img && img.dataset.origSrc) {
const idMatch = img.dataset.origSrc.match(/\/t\/(\d+)\.webp/);
if (idMatch) img.src = `/t/${idMatch[1]}_blur.webp`;
}
};
document.addEventListener('mouseover', _notifThumbHoverIn);
document.addEventListener('mouseout', _notifThumbHoverOut);
// Close on clicking 'View all' or 'Manage subscriptions'
this.dropdown.querySelectorAll('a').forEach(link => {
link.addEventListener('click', () => {
@@ -6938,6 +7008,7 @@ class NotificationSystem {
return;
}
this.list.innerHTML = Sanitizer.clean(items.map(n => this.renderItem(n)).join(''));
if (window.applyNotifThumbBlur) window.applyNotifThumbBlur(this.list);
}
renderHistoryItem(n) {