diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css index 223f633..6017c9b 100644 --- a/public/s/css/f0ckm.css +++ b/public/s/css/f0ckm.css @@ -71,7 +71,7 @@ html[theme='f0ck'] { --metadata-bg: #2b2b2b; --badge-bg: #171717; --posts-meta-bg: #000000b8; - --badge-sfw: #68a728; + --badge-sfw: #02500b; --badge-nsfw: #E10DC3; --badge-nsfl: #660000; --badge-tag: #090909; @@ -132,7 +132,7 @@ html[theme='p1nk'] { --pagination-background-hover: #333; --pagination-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); --badge-bg: #171717; - --badge-sfw: #68a728; + --badge-sfw: #02500b; --badge-nsfw: #E10DC3; --badge-nsfl: #660000; --badge-tag: #090909; @@ -193,7 +193,7 @@ html[theme='orange'] { --badge-bg: #151515; --posts-meta-bg: #000000b8; --badge-tag: #090909; - --badge-sfw: #68a728; + --badge-sfw: #02500b; --badge-nsfw: #E10DC3; --badge-nsfl: #660000; --scrollbar-color: #555; @@ -250,7 +250,7 @@ html[theme='amoled'] { --metadata-bg: #000; --badge-bg: #000; --posts-meta-bg: #000000b8; - --badge-sfw: #68a728; + --badge-sfw: #02500b; --badge-nsfw: #E10DC3; --badge-nsfl: #660000; --badge-tag: #1a1a1a; @@ -347,7 +347,7 @@ html[theme='paper'], --metadata-bg: #ffffff; --badge-bg: #f1f5f9; --posts-meta-bg: rgba(255, 255, 255, 0.94); - --badge-sfw: #16a34a; + --badge-sfw: #02500b; --badge-nsfw: #db2777; --badge-nsfl: #dc2626; --badge-tag: #f1f5f9; @@ -5192,10 +5192,17 @@ h5 { margin-right: 0.5rem !important; } +/* Thumbnail size — driven by data-thumb-size on (set by scroller grid-mode picker) */ +:root { --thumb-min: 118px; } +html[data-thumb-size="s"] { --thumb-min: 80px; } +html[data-thumb-size="m"] { --thumb-min: 118px; } +html[data-thumb-size="l"] { --thumb-min: 180px; } +html[data-thumb-size="xl"] { --thumb-min: 260px; } + div.posts { width: 100%; display: grid; - grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(var(--thumb-min, 118px), 1fr)); grid-auto-flow: dense; justify-items: center; grid-gap: 5px; @@ -5204,9 +5211,9 @@ div.posts { margin-bottom: 35px; } -/* Mobile: Ensure 3 columns of thumbnails */ +/* Mobile: Ensure at least 3 columns unless user picked a larger size */ @media (max-width: 600px) { - div.posts { + html:not([data-thumb-size="l"]):not([data-thumb-size="xl"]) div.posts { grid-template-columns: repeat(3, 1fr); } } @@ -6918,6 +6925,7 @@ a.removetag i { .badge-german { background: linear-gradient(180deg, black 33.33%, red 33.33%, red 66.66%, yellow 66.66%) !important; + box-shadow: 0px 0px 1px white; } .badge-dutch { @@ -15789,6 +15797,53 @@ body.layout-modern .xd-score-wrapper { background: rgba(0, 0, 0, 0.2); } +/* ─── Grid mode / thumbnail size picker (filter modal) ────────────── */ +.nav-grid-mode { + margin-top: 14px; + padding-top: 12px; + border-top: 1px solid rgba(255, 255, 255, 0.07); +} + +.nav-grid-mode-label { + display: block; + font-size: 0.8em; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #888; + margin-bottom: 8px; +} + +.nav-grid-size-btns { + display: flex; + gap: 6px; +} + +.nav-grid-size-btn { + flex: 1; + padding: 7px 10px; + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.12); + color: rgba(255, 255, 255, 0.75); + font-size: 0.82em; + font-weight: 700; + font-family: var(--font, monospace); + cursor: pointer; + border-radius: 4px; + transition: background 0.14s, border-color 0.14s, color 0.14s; + letter-spacing: 0.04em; +} + +.nav-grid-size-btn:hover { + background: rgba(255, 255, 255, 0.13); + color: #fff; +} + +.nav-grid-size-btn.active { + background: var(--accent); + border-color: var(--accent); + color: #000; +} + /* ─── xD score wrapper ─────────────────────────────────────────────── */ .xd-score-wrapper { text-align: right; @@ -18892,6 +18947,7 @@ html:has(body.onara-modal-open) { body.onara-modal-open .pagewrapper { pointer-events: none !important; user-select: none !important; + margin-top: var(--navbar-h, 50px) !important; } body.onara-modal-open .pagewrapper > *:not(.index-layout-wrapper):not(#main), @@ -18938,7 +18994,7 @@ body.onara-modal-open nav.navbar { filter: none !important; pointer-events: auto !important; z-index: 10060 !important; - position: sticky !important; + position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; @@ -18975,7 +19031,7 @@ body.onara-modal-open #sidebar-drag-zone { z-index: 10050 !important; display: none; flex-direction: column !important; - background: rgba(10, 12, 16, 0.72) !important; + background: rgba(0, 0, 0, 0.70) !important; backdrop-filter: blur(5px) saturate(130%) !important; -webkit-backdrop-filter: blur(5px) saturate(130%) !important; overflow-y: auto !important; diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index e028ca2..866044a 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -1155,10 +1155,26 @@ window.cancelAnimFrame = (function () { const urlObj = new URL(itemUrl, window.location.origin); let path = urlObj.pathname.replace(/\/+$/, ''); path = path.replace(/\/(?:\d+|[a-zA-Z0-9_-]{11})$/, ''); - const postsEl = document.querySelector('.posts[data-current-page]'); - const currentPage = postsEl ? parseInt(postsEl.dataset.currentPage, 10) : 1; - const currentGridPath = window._onaraCurrentGridUrl ? new URL(window._onaraCurrentGridUrl, window.location.origin).pathname.replace(/\/p\/\d+/, '').replace(/\/+$/, '') : ''; - if (currentPage > 1 && !path.includes('/p/') && (!currentGridPath || currentGridPath === path)) { + + // Check current grid URL if available and matching base path + if (window._onaraCurrentGridUrl) { + try { + const gridObj = new URL(window._onaraCurrentGridUrl, window.location.origin); + const gridBasePath = gridObj.pathname.replace(/\/p\/\d+/, '').replace(/\/+$/, ''); + if (gridBasePath === path) { + return gridObj.pathname + (gridObj.search || '') + (gridObj.hash || ''); + } + } catch {} + } + + const postsEl = document.querySelector('.posts'); + const activePagSpan = document.querySelector('.pagination span.btn.disabled, .pagination span.disabled'); + const pagPage = activePagSpan ? parseInt(activePagSpan.textContent.trim(), 10) : 0; + const currentPage = (postsEl && postsEl.dataset.currentPage) + ? parseInt(postsEl.dataset.currentPage, 10) + : (pagPage || postsEl?._infiniteState?.currentPage || postsEl?._infiniteState?.visiblePage || 1); + + if (currentPage > 1 && !path.includes('/p/')) { path = (path ? path : '') + '/p/' + currentPage; } if (!path) path = '/'; @@ -1216,6 +1232,14 @@ window.cancelAnimFrame = (function () { if (window.initBackground) window.initBackground(); } + // Restore pagination container visibility on background grid + document.querySelectorAll('.pagination-container-fluid').forEach(el => { + const pagWrapper = el.querySelector('.pagination-wrapper'); + if (!pagWrapper || pagWrapper.innerHTML.trim().length > 0) { + el.style.display = ''; + } + }); + if (!options.skipHistory) { const returnUrl = window._onaraReturnUrl || getOnaraBaseUrl(); const returnTitle = window._onaraReturnTitle || window.f0ckDomain || ''; @@ -3971,7 +3995,16 @@ window.cancelAnimFrame = (function () { // Clear and Hide navbar pagination for Item View (Never show grid pagination on item view) // document.querySelectorAll('.pagination-wrapper').forEach(el => el.innerHTML = ''); // Don't destroy content (cached grid needs it) - document.querySelectorAll('.pagination-container-fluid').forEach(el => el.style.display = 'none'); + if (!useOnara) { + document.querySelectorAll('.pagination-container-fluid').forEach(el => el.style.display = 'none'); + } else { + document.querySelectorAll('.pagination-container-fluid').forEach(el => { + const pagWrapper = el.querySelector('.pagination-wrapper'); + if (!pagWrapper || pagWrapper.innerHTML.trim().length > 0) { + el.style.display = ''; + } + }); + } if (keySegments.length === 0) { console.warn("loadItemAjax: No ID or slug match found in URL", url); @@ -5740,7 +5773,7 @@ window.cancelAnimFrame = (function () { } } - delete postsContainer.dataset.currentPage; // Clear after reading + postsContainer.dataset.currentPage = p; } // Track initially loaded page @@ -5823,6 +5856,7 @@ window.cancelAnimFrame = (function () { // Update URL infiniteState.visiblePage = newPage; + if (postsContainer) postsContainer.dataset.currentPage = newPage; history.replaceState({}, '', buildUrl(newPage)); // Update Pagination @@ -6882,6 +6916,33 @@ window.cancelAnimFrame = (function () { } }); } + + // ── Grid Mode / thumbnail size picker ──────────────────────────────── + const THUMB_SIZE_KEY = 'scroller_thumb_size'; + const THUMB_SIZES = ['s', 'm', 'l', 'xl']; + const gridSizeBtns = overlay.querySelectorAll('.nav-grid-size-btn'); + + const applyNavThumbSize = (size) => { + if (!THUMB_SIZES.includes(size)) size = 'm'; + document.documentElement.dataset.thumbSize = size; + localStorage.setItem(THUMB_SIZE_KEY, size); + gridSizeBtns.forEach(b => b.classList.toggle('active', b.dataset.thumbSize === size)); + }; + + // Sync active state each time the modal opens + if (filterBtn) { + filterBtn.addEventListener('click', () => { + const cur = localStorage.getItem(THUMB_SIZE_KEY) || 'm'; + gridSizeBtns.forEach(b => b.classList.toggle('active', b.dataset.thumbSize === cur)); + }); + } + + gridSizeBtns.forEach(btn => { + btn.addEventListener('click', () => applyNavThumbSize(btn.dataset.thumbSize)); + }); + + // Apply persisted size immediately on init (every page load) + applyNavThumbSize(localStorage.getItem(THUMB_SIZE_KEY) || 'm'); }; document.addEventListener('DOMContentLoaded', () => { @@ -7662,26 +7723,49 @@ window.cancelAnimFrame = (function () { // At ≤599px the sidebar is a pure overlay — never animate wrapper padding const animateWrappers = window.innerWidth > 599; - // Grid/pagewrapper: snap instantly in both directions (no animated reflow). - // Pagination always animates — it tracks sidebar position with no reflow cost. + // All wrappers animate in sync with the sidebar so content and sidebar + // expand/collapse together rather than staggering. const wrappers = getSidebarWrappers(); if (animateWrappers) { wrappers.forEach(w => { const isPagination = w.classList.contains('pagination-container-fluid'); const isMessages = w.classList.contains('messages-convo-page'); - + const prop = (isPagination || isMessages) ? 'right' : 'padding-right'; const targetVal = isHidden ? sidebarWidth + 'px' : '0px'; - - // Lightweight layouts (pagination, messages) animate smoothly. - // Heavy/reflow-intensive layouts snap instantly to avoid reflow lag. - const shouldAnimate = (isPagination || isMessages); - w.style.setProperty('transition', shouldAnimate ? `${prop} 0.3s ease-in-out` : 'none', 'important'); + + w.style.setProperty('transition', `${prop} 0.3s ease-in-out`, 'important'); w.style.setProperty(prop, targetVal, 'important'); }); void sidebar.offsetWidth; // settle layout before sidebar transform begins } + // Animate the drag-zone pill in sync with the sidebar so it stays + // glued to the sidebar's left edge throughout the transition. + edgeZone.style.setProperty('transition', 'right 0.3s ease-in-out', 'important'); + if (isHidden) { + // Opening: pill moves from right-edge → left of sidebar + edgeZone.style.right = sidebarWidth + 'px'; + edgeZone.style.width = '25px'; + edgeZone.style.pointerEvents = 'all'; + } else { + // Closing: pill moves toward right-edge + edgeZone.style.right = '0px'; + edgeZone.style.width = '30px'; + edgeZone.style.pointerEvents = 'all'; + } + + // Animate the Onara modal wrapper's right edge in sync with the sidebar + // so the modal resizes at the same time instead of waiting for settle. + const onaraModal = document.getElementById('onara-modal'); + if (onaraModal && animateWrappers) { + const targetModalRight = isHidden ? sidebarWidth + 'px' : '0px'; + onaraModal.style.setProperty('transition', + 'opacity 0.28s cubic-bezier(0.16,1,0.3,1), transform 0.28s cubic-bezier(0.16,1,0.3,1), right 0.3s ease-in-out', + 'important'); + onaraModal.style.setProperty('right', targetModalRight, 'important'); + } + sidebar.style.setProperty('transition', 'transform 0.3s ease-in-out', 'important'); sidebar.style.transform = `translateX(${targetTranslate}px)`; @@ -7689,9 +7773,11 @@ window.cancelAnimFrame = (function () { const settle = () => { if (settled) return; settled = true; - // Freeze wrapper transitions so the class commit doesn't re-trigger them + // Freeze wrapper and edgeZone transitions so the class commit doesn't re-trigger them wrappers.forEach(w => w.style.setProperty('transition', 'none', 'important')); sidebar.style.setProperty('transition', 'none', 'important'); + edgeZone.style.setProperty('transition', 'none', 'important'); + if (onaraModal) onaraModal.style.setProperty('transition', 'none', 'important'); const toHidden = !isHidden; document.body.classList.toggle('sidebar-right-hidden', toHidden); localStorage.setItem('sidebarRightHidden', toHidden); @@ -7699,6 +7785,11 @@ window.cancelAnimFrame = (function () { sidebar.style.transform = ''; requestAnimationFrame(() => { sidebar.style.removeProperty('transition'); + edgeZone.style.removeProperty('transition'); + if (onaraModal) { + onaraModal.style.removeProperty('transition'); + onaraModal.style.removeProperty('right'); + } wrappers.forEach(w => { w.style.removeProperty('transition'); w.style.removeProperty('padding-right'); diff --git a/public/s/js/scroller.js b/public/s/js/scroller.js index 0cfe672..56bb7f1 100644 --- a/public/s/js/scroller.js +++ b/public/s/js/scroller.js @@ -79,8 +79,9 @@ const CACHE_MAX = 200; const CACHE_TTL = 30 * 60 * 1000; const VOL_KEY = 'scroller_volume'; - const PREFS_KEY = 'scroller_prefs'; - const PRESETS_KEY = 'scroller_presets'; + const PREFS_KEY = 'scroller_prefs'; + const PRESETS_KEY = 'scroller_presets'; + const THUMB_SIZE_KEY = 'scroller_thumb_size'; // ── User Preferences ────────────────────────────────────────────────────── function loadPrefs() { @@ -97,6 +98,33 @@ let autoNextLoops = Math.max(0, parseInt(prefs.autoNextLoops ?? 1, 10)); let leftHandEnabled = prefs.leftHand === true; + // ── Grid / Thumbnail size ───────────────────────────────────────────────── + const THUMB_SIZES = ['s', 'm', 'l', 'xl']; + let thumbSize = localStorage.getItem(THUMB_SIZE_KEY) || 'm'; + if (!THUMB_SIZES.includes(thumbSize)) thumbSize = 'm'; + + function applyThumbSize(size) { + thumbSize = size; + document.documentElement.dataset.thumbSize = size; + localStorage.setItem(THUMB_SIZE_KEY, size); + // Sync pill active state whenever called outside the click handler + document.querySelectorAll('#thumb-size-pills .filter-pill').forEach(p => + p.classList.toggle('active', p.dataset.thumbSize === size) + ); + } + // Apply persisted size on load + applyThumbSize(thumbSize); + + // Wire pill click events + const thumbSizePillsEl = document.getElementById('thumb-size-pills'); + if (thumbSizePillsEl) { + thumbSizePillsEl.querySelectorAll('.filter-pill').forEach(pill => { + pill.addEventListener('click', () => { + applyThumbSize(pill.dataset.thumbSize); + }); + }); + } + let applied = { mode: defaultMode, mime: '', order: 'random', tags: [], externalUrl: null }; let pending = { ...applied, tags: [] }; @@ -2988,6 +3016,7 @@ document.querySelectorAll('#mode-pills .filter-pill').forEach(p => p.classList.toggle('active', +p.dataset.mode === pending.mode)); document.querySelectorAll('#mime-pills .filter-pill').forEach(p => p.classList.toggle('active', p.dataset.mime === pending.mime)); document.querySelectorAll('#order-pills .filter-pill').forEach(p => p.classList.toggle('active', p.dataset.order === pending.order)); + document.querySelectorAll('#thumb-size-pills .filter-pill').forEach(p => p.classList.toggle('active', p.dataset.thumbSize === thumbSize)); if (externalUrlInput) externalUrlInput.value = pending.externalUrl || ''; renderActiveTags(); } diff --git a/src/inc/routes/index.mjs b/src/inc/routes/index.mjs index 259171d..8f64478 100644 --- a/src/inc/routes/index.mjs +++ b/src/inc/routes/index.mjs @@ -153,7 +153,11 @@ export default (router, tpl) => { }; userData.age_days = Math.floor((Date.now() - new Date(userData.created_at).getTime()) / 86400000); - if (userData.banned) { + if (!req.session) { + userData.banned = false; + delete userData.ban_expires; + delete userData.ban_duration; + } else if (userData.banned) { if (!userData.ban_expires) { userData.ban_duration = "Permanent"; } else { @@ -412,7 +416,7 @@ export default (router, tpl) => { if (mode === 'item' && isOnara) { const itemHtml = tpl.render('ajax-item', data, req); - const isRandom = req.cookies.random_mode === '1'; + const isRandom = req.cookies.random_mode === '1' || req.url.qs?.random === '1' || req.query?.random === '1'; let itemPage = 1; if (req.params.page) { itemPage = parseInt(req.params.page, 10) || 1; diff --git a/src/inc/routes/random.mjs b/src/inc/routes/random.mjs index 45e0fae..9d82550 100644 --- a/src/inc/routes/random.mjs +++ b/src/inc/routes/random.mjs @@ -68,7 +68,7 @@ export default (router, tpl) => { }); } - res.redirect(encodeURI(`${data.link.main}${data.link.path}${data.itemid}${data.link.suffix || ''}`)); + res.redirect(encodeURI(`${data.link.main}${data.link.path}${data.itemid}${data.link.suffix || ''}`) + '?random=1'); }); return router; diff --git a/views/item-partial-legacy.html b/views/item-partial-legacy.html index bd86119..9f9bff3 100644 --- a/views/item-partial-legacy.html +++ b/views/item-partial-legacy.html @@ -174,7 +174,7 @@ @if(!item.is_sfw && !item.is_nsfw && !item.is_nsfl) - untagged + ? @endif @if(typeof item.tags !== "undefined") diff --git a/views/item-partial-modern.html b/views/item-partial-modern.html index 73c0763..7ae642b 100644 --- a/views/item-partial-modern.html +++ b/views/item-partial-modern.html @@ -43,7 +43,7 @@ @if(!item.is_sfw && !item.is_nsfw && !item.is_nsfl) - untagged + ? @endif @if(typeof item.tags !== "undefined") diff --git a/views/scroller.html b/views/scroller.html index 7824aa4..5fc3f07 100644 --- a/views/scroller.html +++ b/views/scroller.html @@ -1152,6 +1152,16 @@ +
+ +
+ + + + +
+
+
diff --git a/views/snippets/excluded-tags-modal.html b/views/snippets/excluded-tags-modal.html index 63dd2e8..6acee67 100644 --- a/views/snippets/excluded-tags-modal.html +++ b/views/snippets/excluded-tags-modal.html @@ -52,5 +52,15 @@
@endif + +
diff --git a/views/user-partial.html b/views/user-partial.html index 12a8310..d59df3e 100644 --- a/views/user-partial.html +++ b/views/user-partial.html @@ -48,7 +48,7 @@
- @if(user.admin)⚡ @elseif(user.is_moderator)🛡 @endif{!! user.display_name || user.user !!}@if(user.is_ghost) LEGACY@endif @if(user.banned) BANNED@endif@if(user.display_name) ({!! user.user !!})@endif + @if(user.admin)⚡ @elseif(user.is_moderator)🛡 @endif{!! user.display_name || user.user !!}@if(user.is_ghost) LEGACY@endif @if(session && user.banned) BANNED@endif@if(user.display_name) ({!! user.user !!})@endif @if(session && session.id === user.user_id)