diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css index 5a00502..7921e4d 100644 --- a/public/s/css/f0ckm.css +++ b/public/s/css/f0ckm.css @@ -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; } \ No newline at end of file diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index ddc043b..149a356 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -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

+ const p = thumb.querySelector('p'); + if (p && !p.querySelector('.hide-thumb-btn')) { + const btn = document.createElement('button'); + btn.className = 'hide-thumb-btn'; + btn.innerHTML = ''; + btn.title = 'Hide thumbnail again'; + p.appendChild(btn); + } + } + } + }, true); })(); diff --git a/public/s/js/settings.js b/public/s/js/settings.js index 46194f8..0a08581 100644 --- a/public/s/js/settings.js +++ b/public/s/js/settings.js @@ -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) { diff --git a/src/inc/locales/de.json b/src/inc/locales/de.json index 0635ab4..62993bb 100644 --- a/src/inc/locales/de.json +++ b/src/inc/locales/de.json @@ -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", diff --git a/src/inc/locales/en.json b/src/inc/locales/en.json index 5a8f456..9d1915e 100644 --- a/src/inc/locales/en.json +++ b/src/inc/locales/en.json @@ -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", diff --git a/src/inc/locales/nl.json b/src/inc/locales/nl.json index bd0c682..e6171e6 100644 --- a/src/inc/locales/nl.json +++ b/src/inc/locales/nl.json @@ -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", diff --git a/src/inc/locales/zange.json b/src/inc/locales/zange.json index 6c0ac3d..78115e2 100644 --- a/src/inc/locales/zange.json +++ b/src/inc/locales/zange.json @@ -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", diff --git a/views/settings.html b/views/settings.html index e3c3233..9946872 100644 --- a/views/settings.html +++ b/views/settings.html @@ -139,6 +139,34 @@ {{ t('settings.enable_bg_blur_hint') }} +

+ + {{ t('settings.blur_nsfw_hint') }} +
+
+ + {{ t('settings.blur_nsfl_hint') }} +
+
+ + {{ t('settings.blur_sfw_hint') }} +
+
+ + {{ t('settings.blur_untagged_hint') }} +