diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css index e49e905..7932cd1 100644 --- a/public/s/css/f0ckm.css +++ b/public/s/css/f0ckm.css @@ -8203,15 +8203,16 @@ input#s_avatar { #search-close, #excluded-tags-close { - position: absolute; - top: 20px; - right: 30px; - color: var(--white); - font-size: 2rem; + position: relative; + top: auto; + right: auto; + color: var(--white, #fff); + font-size: 1.5rem; cursor: pointer; font-family: sans-serif; opacity: 0.7; transition: opacity 0.2s; + line-height: 1; } #search-close:hover, @@ -8219,6 +8220,106 @@ input#s_avatar { opacity: 1; } +/* Filter Modal Card & Body Structure */ +#excluded-tags-overlay .filter-modal-container { + background: var(--nav-bg, rgba(18, 18, 20, 0.96)); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 14px; + width: 100%; + max-width: 600px; + max-height: 85vh; + display: flex; + flex-direction: column; + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.7); + padding: 0; + overflow: hidden; + color: var(--white, #fff); + position: relative; +} + +.filter-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.02); +} + +.filter-modal-title { + margin: 0; + font-size: 1.15em; + font-weight: 600; + color: var(--white, #fff); + display: flex; + align-items: center; + gap: 10px; +} + +.filter-modal-body { + padding: 20px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 18px; +} + +.modal-filter-group { + display: flex; + flex-direction: column; + gap: 8px; +} + +.modal-filter-label { + font-size: 0.82em; + text-transform: uppercase; + letter-spacing: 0.5px; + color: rgba(255, 255, 255, 0.6); + font-weight: 600; + display: flex; + align-items: center; + gap: 6px; + margin: 0; +} + +.filter-timeframe-subgroup { + display: none; +} + +.filter-timeframe-subgroup.visible { + display: block; +} + +.filter-modal-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 20px; + border-top: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(0, 0, 0, 0.25); +} + +.filter-apply-btn { + background: var(--accent, #1fb2b0); + color: var(--bg, #000); + border: none; + border-radius: 6px; + padding: 8px 18px; + font-weight: 600; + cursor: pointer; + display: flex; + align-items: center; + gap: 6px; + transition: all 0.2s ease; +} + +.filter-apply-btn:hover { + filter: brightness(1.15); + transform: translateY(-1px); +} + /* Delete Tag Modal */ .modal-overlay { position: fixed; @@ -17549,4 +17650,305 @@ a.lazy-thumb.filtered-upload-ghost:hover::after { .item-main-content { padding-left: 15px; padding-right: 15px; -} \ No newline at end of file +} + +/* ========================================================================== + MAINPAGE OVERHAUL: FILTER BAR & MULTI-LAYOUT PRESENTATIONS + ========================================================================== */ + +.mainpage-filter-bar { + position: sticky; + top: calc(var(--navbar-h, 50px) + 8px); + z-index: 100; + display: flex; + flex-direction: column; + gap: 10px; + background: var(--nav-bg, rgba(18, 18, 20, 0.92)); + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 10px; + padding: 12px 16px; + margin-bottom: 18px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +.mainpage-filter-bar:hover { + border-color: rgba(255, 255, 255, 0.15); +} + +.filter-bar-row { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 12px 18px; + width: 100%; +} + +.filter-bar-row.row-secondary { + padding-top: 10px; + border-top: 1px solid rgba(255, 255, 255, 0.06); +} + +.filter-group-left { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 12px 18px; +} + +.filter-group-right { + display: flex; + align-items: center; + gap: 10px; + margin-left: auto; +} + +.filter-section { + display: flex; + align-items: center; + gap: 8px; +} + +.filter-label { + font-size: 0.78rem; + font-weight: 600; + color: var(--text-muted, #888); + text-transform: uppercase; + letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 4px; +} + +.filter-btn-group { + display: inline-flex; + background: rgba(0, 0, 0, 0.3); + border-radius: 6px; + padding: 2px; + border: 1px solid rgba(255, 255, 255, 0.05); +} + +.filter-btn { + background: transparent; + border: none; + color: var(--text-muted, #aaa); + padding: 4px 10px; + font-size: 0.82rem; + font-weight: 500; + border-radius: 4px; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 5px; + transition: all 0.15s ease; +} + +.filter-btn:hover { + color: #fff; + background: rgba(255, 255, 255, 0.08); +} + +.filter-btn.active { + background: var(--accent, #1fb2b0); + color: #000; + font-weight: 700; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); +} + +.filter-toggle-pill { + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(255, 255, 255, 0.1); + color: var(--text-muted, #aaa); + padding: 4px 10px; + font-size: 0.8rem; + border-radius: 20px; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 5px; + transition: all 0.15s ease; +} + +.filter-toggle-pill:hover { + border-color: var(--accent, #1fb2b0); + color: #fff; +} + +.filter-toggle-pill.active { + background: rgba(31, 178, 176, 0.2); + border-color: var(--accent, #1fb2b0); + color: var(--accent, #1fb2b0); + font-weight: 600; +} + +.filter-toggle-pill.rating-sfw.active { + background: rgba(34, 197, 94, 0.2); + border-color: #22c55e; + color: #4ade80; +} + +.filter-toggle-pill.rating-nsfw.active { + background: rgba(249, 115, 22, 0.2); + border-color: #f97316; + color: #fb923c; +} + +.filter-toggle-pill.rating-nsfl.active { + background: rgba(239, 68, 68, 0.2); + border-color: #ef4444; + color: #f87171; +} + +.filter-toggle-pill.rating-untagged.active { + background: rgba(168, 85, 247, 0.2); + border-color: #a855f7; + color: #c084fc; +} + +.filter-timeframe { + display: none; +} + +.filter-timeframe.visible { + display: flex; +} + +.filter-reset-btn { + background: transparent; + border: none; + color: #888; + font-size: 0.78rem; + cursor: pointer; + padding: 4px 8px; + border-radius: 4px; + transition: color 0.2s ease; + + margin-left: auto; +} + +.filter-reset-btn:hover { + color: #ef4444; +} + +/* === LAYOUT MODES === */ + +/* Masonry Layout */ +.posts.layout-masonry { + column-count: 5; + column-gap: 10px; + display: block; +} + +@media (max-width: 1400px) { .posts.layout-masonry { column-count: 4; } } +@media (max-width: 1000px) { .posts.layout-masonry { column-count: 3; } } +@media (max-width: 650px) { .posts.layout-masonry { column-count: 2; } } + +.posts.layout-masonry .thumb { + break-inside: avoid; + margin-bottom: 10px; + display: block; + width: 100% !important; + height: auto !important; +} + +/* Stream / Vertical Feed Layout */ +.posts.layout-stream { + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; + width: 100%; + max-width: 680px; + margin: 0 auto; +} + +.stream-card { + width: 100%; + background: var(--nav-bg, #181818); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 10px; + overflow: hidden; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); + transition: transform 0.2s ease, border-color 0.2s ease; +} + +.stream-card:hover { + border-color: rgba(255, 255, 255, 0.18); +} + +.stream-card-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 14px; + background: rgba(0, 0, 0, 0.2); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.stream-author { + font-weight: 600; + font-size: 0.95rem; + color: #fff; + text-decoration: none; +} + +.stream-author:hover { + text-decoration: underline; +} + +.stream-meta { + display: flex; + align-items: center; + gap: 8px; +} + +.stream-media-wrapper { + position: relative; + display: block; + width: 100%; + background: #000; + text-align: center; +} + +.stream-media-img { + max-width: 100%; + max-height: 550px; + object-fit: contain; + display: block; + margin: 0 auto; +} + +.stream-media-badge { + position: absolute; + top: 10px; + right: 10px; + background: rgba(0, 0, 0, 0.7); + color: #fff; + font-size: 0.7rem; + font-weight: 700; + padding: 3px 7px; + border-radius: 4px; + backdrop-filter: blur(4px); +} + +.stream-card-footer { + padding: 10px 14px; + background: rgba(0, 0, 0, 0.2); + border-top: 1px solid rgba(255, 255, 255, 0.05); + display: flex; + justify-content: flex-end; +} + +.stream-action-btn { + color: var(--accent, #1fb2b0); + text-decoration: none; + font-size: 0.85rem; + font-weight: 600; +} + +.stream-action-btn:hover { + text-decoration: underline; +} diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index caa87e6..7bfa40c 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -692,18 +692,18 @@ window.cancelAnimFrame = (function () { else if (window.activeMode === 4) activeRatings = ['nsfl']; else if (window.activeMode === 2) activeRatings = ['untagged']; } - const selector = document.getElementById('rating-selector'); - if (!selector) return; - selector.querySelectorAll('.rating-toggle-btn').forEach(btn => { - const r = btn.dataset.rating; - if (!r) return; // ALL button handled separately - btn.classList.toggle('active', activeRatings.includes(r)); + const containers = document.querySelectorAll('#rating-selector, .rating-group'); + containers.forEach(container => { + container.querySelectorAll('.rating-toggle-btn').forEach(btn => { + const r = btn.dataset.rating; + if (!r) return; // ALL button handled separately + btn.classList.toggle('active', activeRatings.includes(r)); + }); + container.querySelectorAll('.rating-toggle-all').forEach(allBtn => { + allBtn.classList.toggle('active', (getRatingsCookie().length === 0 && window.activeMode === 3) || activeRatings.length === 0); + }); }); - // ALL button: active when ratings cookie is empty/absent (server mode is the authority) - const allBtn = document.getElementById('rating-btn-all'); - if (allBtn) { - allBtn.classList.toggle('active', (getRatingsCookie().length === 0 && window.activeMode === 3) || activeRatings.length === 0); - } + if (window.updateFilterBadge) window.updateFilterBadge(); }; // Wire up rating toggle buttons @@ -2280,8 +2280,10 @@ window.cancelAnimFrame = (function () { } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initRuffle, { once: true }); + document.addEventListener('DOMContentLoaded', () => { if (window.initMainpageFilterBar) window.initMainpageFilterBar(); }); } else { initRuffle(); + if (window.initMainpageFilterBar) window.initMainpageFilterBar(); } document.addEventListener('f0ck:contentLoaded', initRuffle); @@ -2313,6 +2315,186 @@ window.cancelAnimFrame = (function () { new ResizeObserver(window.syncNavbarHeight).observe(_navEl); } + // ── Global Filter Bar Event Delegation ────────────────────────────────── + document.addEventListener('click', (e) => { + // 1. Layout button click (grid, masonry, stream) + const layoutBtn = e.target.closest('.layout-btn'); + if (layoutBtn) { + e.preventDefault(); + const layout = layoutBtn.dataset.layout; + const container = layoutBtn.closest('.filter-modal-container, #mainpage-filter-bar, .modal-filter-group') || document; + container.querySelectorAll('.layout-btn').forEach(b => b.classList.toggle('active', b === layoutBtn)); + + const postsContainer = document.getElementById('posts-container'); + if (postsContainer) { + postsContainer.className = `posts layout-${layout} grid-transition show`; + postsContainer.id = 'posts-container'; + postsContainer.dataset.layout = layout; + } + + document.cookie = `layout=${layout}; path=/; max-age=31536000`; + const u = new URL(window.location.href); + if (!layout || layout === 'grid') u.searchParams.delete('layout'); + else u.searchParams.set('layout', layout); + + const fromModal = !!layoutBtn.closest('#excluded-tags-overlay'); + if (fromModal) window._keepFilterModal = true; + + if (window.gridCacheMap) window.gridCacheMap.clear(); + if (typeof loadPageAjax === 'function') { + const p = loadPageAjax(u.pathname + u.search, true, { bypassCache: true }); + if (fromModal) Promise.resolve(p).finally(() => { window._keepFilterModal = false; }); + } else { + window.location.href = u.pathname + u.search; + } + return; + } + + // 2. Sort button click + const sortBtn = e.target.closest('.sort-btn'); + if (sortBtn) { + e.preventDefault(); + const sort = sortBtn.dataset.sort; + const container = sortBtn.closest('.filter-modal-container, #mainpage-filter-bar, .modal-filter-group') || document; + container.querySelectorAll('.sort-btn').forEach(b => b.classList.toggle('active', b === sortBtn)); + + const timeframeSec = document.getElementById('filter-timeframe-section'); + if (timeframeSec) { + timeframeSec.classList.toggle('visible', ['popular', 'top', 'xd', 'comments'].includes(sort)); + } + + document.cookie = `sort=${sort}; path=/; max-age=31536000`; + const u = new URL(window.location.href); + if (!sort || sort === 'newest') u.searchParams.delete('sort'); + else u.searchParams.set('sort', sort); + + const fromModal = !!sortBtn.closest('#excluded-tags-overlay'); + if (fromModal) window._keepFilterModal = true; + + if (window.gridCacheMap) window.gridCacheMap.clear(); + if (typeof loadPageAjax === 'function') { + const p = loadPageAjax(u.pathname + u.search, true, { bypassCache: true }); + if (fromModal) Promise.resolve(p).finally(() => { window._keepFilterModal = false; }); + } else { + window.location.href = u.pathname + u.search; + } + return; + } + + // 3. Timeframe button click + const tfBtn = e.target.closest('.timeframe-btn'); + if (tfBtn) { + e.preventDefault(); + const timeframe = tfBtn.dataset.timeframe; + const container = tfBtn.closest('.filter-modal-container, #mainpage-filter-bar, .modal-filter-group') || document; + container.querySelectorAll('.timeframe-btn').forEach(b => b.classList.toggle('active', b === tfBtn)); + + document.cookie = `timeframe=${timeframe}; path=/; max-age=31536000`; + const u = new URL(window.location.href); + if (!timeframe || timeframe === 'all') u.searchParams.delete('timeframe'); + else u.searchParams.set('timeframe', timeframe); + + const fromModal = !!tfBtn.closest('#excluded-tags-overlay'); + if (fromModal) window._keepFilterModal = true; + + if (window.gridCacheMap) window.gridCacheMap.clear(); + if (typeof loadPageAjax === 'function') { + const p = loadPageAjax(u.pathname + u.search, true, { bypassCache: true }); + if (fromModal) Promise.resolve(p).finally(() => { window._keepFilterModal = false; }); + } else { + window.location.href = u.pathname + u.search; + } + return; + } + + // 4. MIME / Media type pill click (image, video, audio, flash) + const mimePill = e.target.closest('.mime-pill'); + if (mimePill) { + e.preventDefault(); + mimePill.classList.toggle('active'); + + const container = mimePill.closest('.filter-modal-container, #mainpage-filter-bar, .mime-group') || document; + const activeMimes = []; + container.querySelectorAll('.mime-pill.active').forEach(p => { + if (p.dataset.mime) activeMimes.push(p.dataset.mime); + }); + + const mimeVal = activeMimes.join(','); + document.cookie = `mime=${encodeURIComponent(mimeVal)}; path=/; max-age=31536000`; + + const u = new URL(window.location.href); + if (activeMimes.length > 0) u.searchParams.set('mime', mimeVal); + else u.searchParams.delete('mime'); + + const fromModal = !!mimePill.closest('#excluded-tags-overlay'); + if (fromModal) window._keepFilterModal = true; + + if (window.gridCacheMap) window.gridCacheMap.clear(); + if (typeof loadPageAjax === 'function') { + const p = loadPageAjax(u.pathname + u.search, true, { bypassCache: true }); + if (fromModal) Promise.resolve(p).finally(() => { window._keepFilterModal = false; }); + } else { + window.location.href = u.pathname + u.search; + } + return; + } + + // 5. Generic attribute toggle pill click (OC, Discussed) + const togglePill = e.target.closest('.filter-toggle-pill[data-toggle]'); + if (togglePill) { + e.preventDefault(); + togglePill.classList.toggle('active'); + const paramName = togglePill.dataset.toggle; + const val = togglePill.classList.contains('active') ? '1' : null; + + const u = new URL(window.location.href); + if (val) u.searchParams.set(paramName, val); + else u.searchParams.delete(paramName); + + const fromModal = !!togglePill.closest('#excluded-tags-overlay'); + if (fromModal) window._keepFilterModal = true; + + if (window.gridCacheMap) window.gridCacheMap.clear(); + if (typeof loadPageAjax === 'function') { + const p = loadPageAjax(u.pathname + u.search, true, { bypassCache: true }); + if (fromModal) Promise.resolve(p).finally(() => { window._keepFilterModal = false; }); + } else { + window.location.href = u.pathname + u.search; + } + return; + } + + // 6. Reset button click + const resetBtn = e.target.closest('#filter-reset-btn'); + if (resetBtn) { + e.preventDefault(); + document.cookie = 'ratings=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'; + document.cookie = 'mime=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'; + document.cookie = 'layout=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'; + document.cookie = 'sort=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'; + document.cookie = 'timeframe=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'; + document.cookie = 'mode=3; path=/; max-age=31536000'; + window.activeMode = 3; + if (window.syncRatingButtonUI) window.syncRatingButtonUI(); + if (window.updateFilterBadge) window.updateFilterBadge(); + + const u = new URL(window.location.href); + ['layout', 'sort', 'timeframe', 'oc', 'has_comments', 'pinned', 'ratings', 'mime', 'mode'].forEach(p => u.searchParams.delete(p)); + + const fromModal = !!resetBtn.closest('#excluded-tags-overlay'); + if (fromModal) window._keepFilterModal = true; + + if (window.gridCacheMap) window.gridCacheMap.clear(); + if (typeof loadPageAjax === 'function') { + const p = loadPageAjax(u.pathname + u.search, true, { bypassCache: true }); + if (fromModal) Promise.resolve(p).finally(() => { window._keepFilterModal = false; }); + } else { + window.location.href = u.pathname + u.search; + } + return; + } + }); + // Sidebar state init — apply persisted hidden/visible state on load window.initSidebarRightToggle = () => { // One-time migration: clear the old mobile-default-hidden value so existing @@ -2590,13 +2772,21 @@ window.cancelAnimFrame = (function () { canvas.classList.add('fader-out'); } - // Rescue existing pagination container before wiping main, so it never disappears from the DOM + // Rescue existing pagination container before wiping main, so it never disappears const existingPagContainer = main.querySelector('.pagination-container-fluid'); // Clear entire main content and create fresh grid structure (with sidebar for index) const indexWrapper = document.createElement('div'); indexWrapper.className = 'index-layout-wrapper'; - indexWrapper.innerHTML = '