jhfg
This commit is contained in:
@@ -7166,25 +7166,44 @@ button#togglebg {
|
|||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── Posts Grid Skeleton: shimmer on real items while thumbnail loads ─────── */
|
/* ─── Posts Grid Skeleton: GPU-composited shimmer while thumbnail loads ───── */
|
||||||
|
/* Uses ::before + transform:translateX instead of background-position. */
|
||||||
|
/* background-position forces a CPU repaint every frame for every element; */
|
||||||
|
/* transform runs on the compositor thread — no main-thread blocking. */
|
||||||
div.posts>a.lazy-thumb:not(.loaded) {
|
div.posts>a.lazy-thumb:not(.loaded) {
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.posts>a.lazy-thumb:not(.loaded)::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
background: linear-gradient(90deg,
|
background: linear-gradient(90deg,
|
||||||
rgba(255, 255, 255, 0.04) 0%,
|
transparent 0%,
|
||||||
rgba(255, 255, 255, 0.10) 40%,
|
rgba(255, 255, 255, 0.10) 50%,
|
||||||
rgba(255, 255, 255, 0.04) 80%);
|
transparent 100%);
|
||||||
background-size: 600px 100%;
|
transform: translateX(-100%);
|
||||||
animation: skeleton-shimmer 1.6s ease-in-out infinite;
|
animation: shimmer-slide 1.6s ease-in-out infinite;
|
||||||
|
will-change: transform;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stagger shimmer phases so items feel alive individually */
|
/* Stagger shimmer phases so items feel alive individually */
|
||||||
div.posts>a.lazy-thumb:not(.loaded):nth-child(3n+2) {
|
div.posts>a.lazy-thumb:not(.loaded):nth-child(3n+2)::before {
|
||||||
animation-delay: 0.2s;
|
animation-delay: 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.posts>a.lazy-thumb:not(.loaded):nth-child(3n+3) {
|
div.posts>a.lazy-thumb:not(.loaded):nth-child(3n+3)::before {
|
||||||
animation-delay: 0.4s;
|
animation-delay: 0.4s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer-slide {
|
||||||
|
0% { transform: translateX(-100%); }
|
||||||
|
100% { transform: translateX(100%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Make individual item entry subtler and faster for infinite scroll */
|
/* Make individual item entry subtler and faster for infinite scroll */
|
||||||
@keyframes fadeInFX {
|
@keyframes fadeInFX {
|
||||||
|
|||||||
@@ -2167,12 +2167,6 @@ window.cancelAnimFrame = (function () {
|
|||||||
|
|
||||||
const currentScroll = window.scrollY;
|
const currentScroll = window.scrollY;
|
||||||
|
|
||||||
// Immediately fade out the current main content to provide a clean slate for the next page
|
|
||||||
if (main) {
|
|
||||||
main.classList.add('grid-transition');
|
|
||||||
main.classList.remove('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save scroll position for the current page before we leave it (only for new navigations)
|
// Save scroll position for the current page before we leave it (only for new navigations)
|
||||||
if (!options.skipPush) {
|
if (!options.skipPush) {
|
||||||
const currentState = history.state || {};
|
const currentState = history.state || {};
|
||||||
@@ -2216,84 +2210,79 @@ window.cancelAnimFrame = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Check for cached grid (Index/Grid Only) - RESTORE EARLY to avoid pagination/layout destruction
|
// Check for cached grid — fires on ANY navigation to a grid URL (not just browser back/forward).
|
||||||
if (isGrid && options.skipPush) {
|
// The fade-out of main must NOT happen before this check: doing so causes the
|
||||||
|
// IntersectionObserver to fire for every cached thumbnail at once when they re-enter
|
||||||
|
// the viewport, loading them all simultaneously instead of progressively.
|
||||||
|
if (isGrid) {
|
||||||
const targetCacheKey = urlObj.pathname + urlObj.search;
|
const targetCacheKey = urlObj.pathname + urlObj.search;
|
||||||
if (gridCacheMap.has(targetCacheKey)) {
|
if (gridCacheMap.has(targetCacheKey)) {
|
||||||
const cache = gridCacheMap.get(targetCacheKey);
|
const cache = gridCacheMap.get(targetCacheKey);
|
||||||
gridCacheMap.delete(targetCacheKey); // Consume cache
|
// Keep cache alive — cleared on mode/filter changes via gridCacheMap.clear()
|
||||||
|
|
||||||
|
if (navbar) navbar.classList.add('pbwork');
|
||||||
stopMedia();
|
stopMedia();
|
||||||
|
|
||||||
// Instant fade out background canvas (from item view)
|
// Fade out background canvas (item view blur)
|
||||||
const canvas = document.getElementById('bg');
|
const canvas = document.getElementById('bg');
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
canvas.style.transition = 'none'; // Disable transition
|
canvas.style.transition = 'none';
|
||||||
canvas.style.opacity = '0';
|
canvas.style.opacity = '0';
|
||||||
canvas.classList.remove('fader-in');
|
canvas.classList.remove('fader-in');
|
||||||
canvas.classList.add('fader-out');
|
canvas.classList.add('fader-out');
|
||||||
|
|
||||||
// Clean up overrides after render
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => { canvas.style.transition = ''; canvas.style.opacity = ''; }, 50);
|
||||||
canvas.style.transition = '';
|
|
||||||
canvas.style.opacity = '';
|
|
||||||
}, 50);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore DOM
|
// Restore DOM
|
||||||
if (main) {
|
if (main) {
|
||||||
main.innerHTML = ''; // Clear item view
|
main.innerHTML = '';
|
||||||
main.appendChild(cache.node);
|
main.appendChild(cache.node);
|
||||||
cache.node.style.display = ''; // Ensure visible
|
cache.node.style.display = '';
|
||||||
cache.node.classList.remove('cached-grid'); // Cleanup
|
cache.node.classList.remove('cached-grid');
|
||||||
main.className = '';
|
main.className = '';
|
||||||
|
|
||||||
// Clear layout-lock state now that we are restoring the grid
|
|
||||||
document.body.classList.remove('legacy-view', 'layout-modern', 'layout-legacy');
|
document.body.classList.remove('legacy-view', 'layout-modern', 'layout-legacy');
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
document.body.style.height = '';
|
document.body.style.height = '';
|
||||||
|
|
||||||
// Restore Pagination HTML
|
|
||||||
const paginationWrapper = document.querySelector('.pagination-wrapper');
|
const paginationWrapper = document.querySelector('.pagination-wrapper');
|
||||||
if (paginationWrapper && cache.pagination) {
|
if (paginationWrapper && cache.pagination) paginationWrapper.innerHTML = cache.pagination;
|
||||||
paginationWrapper.innerHTML = cache.pagination;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-attach Infinite Scroll Handler
|
|
||||||
const restoredPosts = cache.node.querySelector('.posts');
|
const restoredPosts = cache.node.querySelector('.posts');
|
||||||
if (restoredPosts && restoredPosts._scrollHandler) {
|
if (restoredPosts && restoredPosts._scrollHandler) {
|
||||||
window.addEventListener('scroll', restoredPosts._scrollHandler);
|
window.addEventListener('scroll', restoredPosts._scrollHandler, { passive: true });
|
||||||
// Ensure loading state is reset just in case
|
|
||||||
if (restoredPosts._infiniteState) restoredPosts._infiniteState.loading = false;
|
if (restoredPosts._infiniteState) restoredPosts._infiniteState.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore Scroll Position
|
// Update URL bar (only for explicit clicks, not browser back/forward which already did it)
|
||||||
|
if (!options.skipPush) history.pushState({}, '', url);
|
||||||
|
|
||||||
|
// Restore scroll position
|
||||||
requestAnimationFrame(() => window.scrollTo(0, cache.scroll));
|
requestAnimationFrame(() => window.scrollTo(0, cache.scroll));
|
||||||
|
|
||||||
// Update Document Title
|
// Update title
|
||||||
let titleSuffix = '';
|
const urlParamsC = new URLSearchParams(urlObj.search);
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const pageC = urlParamsC.get('p') || urlParamsC.get('page');
|
||||||
const page = urlParams.get('p') || urlParams.get('page');
|
document.title = `${window.f0ckDomain}${pageC ? ` - page ${pageC}` : ''}`;
|
||||||
if (page) titleSuffix = ` - page ${page}`;
|
|
||||||
document.title = `${window.f0ckDomain}${titleSuffix}`;
|
document.querySelectorAll('.pagination-container-fluid').forEach(el => el.style.display = '');
|
||||||
|
if (navbar) navbar.classList.remove('pbwork');
|
||||||
// Reset navigation state
|
window.updateVisitIndicators();
|
||||||
document.querySelectorAll('.pagination-container-fluid').forEach(el => el.style.display = ''); // Restore pagination visibility
|
window.initLazyLoading();
|
||||||
if (navbar) navbar.classList.remove("pbwork");
|
if (window.initSidebarRightToggle) window.initSidebarRightToggle();
|
||||||
window.updateVisitIndicators();
|
if (window.syncNavbarHeight) window.syncNavbarHeight();
|
||||||
window.initLazyLoading();
|
window.NotificationSystemInstance?.pollDebounced?.();
|
||||||
if (window.initSidebarRightToggle) window.initSidebarRightToggle();
|
isNavigating = false;
|
||||||
if (window.syncNavbarHeight) window.syncNavbarHeight();
|
return; // SKIP FETCH
|
||||||
// Sync has-notif highlights — on PWA there's no visibilitychange, so poll here
|
|
||||||
window.NotificationSystemInstance?.pollDebounced?.();
|
|
||||||
isNavigating = false;
|
|
||||||
return; // SKIP FETCH
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache miss — full fetch path. Fade out now (safe: no cached thumbnails in DOM).
|
||||||
|
if (main) {
|
||||||
|
main.classList.add('grid-transition');
|
||||||
|
main.classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Handle transition from Item View or User Profile back to Grid View
|
// Handle transition from Item View or User Profile back to Grid View
|
||||||
const isOnProfilePage = main && main.querySelector('.profile_head');
|
const isOnProfilePage = main && main.querySelector('.profile_head');
|
||||||
@@ -3695,6 +3684,12 @@ window.cancelAnimFrame = (function () {
|
|||||||
targetUrl = targetUrl.replace(/[?&]strict=1/, '').replace(/[?&]$/, '');
|
targetUrl = targetUrl.replace(/[?&]strict=1/, '').replace(/[?&]$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Already on this exact grid page — just scroll to top, no reload
|
||||||
|
if (targetUrl.split('#')[0] === window.location.href.split('#')[0] && !anyLink.hash && document.querySelector('.posts')) {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const parts = pathname.split('/').filter(Boolean);
|
const parts = pathname.split('/').filter(Boolean);
|
||||||
const isItemLink = !pathname.match(/\/p\//) && (
|
const isItemLink = !pathname.match(/\/p\//) && (
|
||||||
pathname.match(/^\/\d+/) ||
|
pathname.match(/^\/\d+/) ||
|
||||||
@@ -4689,39 +4684,48 @@ window.cancelAnimFrame = (function () {
|
|||||||
// Scroll detection - preload before reaching bottom
|
// Scroll detection - preload before reaching bottom
|
||||||
const PRELOAD_OFFSET = 500; // pixels before bottom to trigger load
|
const PRELOAD_OFFSET = 500; // pixels before bottom to trigger load
|
||||||
|
|
||||||
|
// RAF throttle: getBoundingClientRect and offsetHeight force synchronous layout
|
||||||
|
// reflows. Without throttling, Chromium recalculates layout on every scroll
|
||||||
|
// event (potentially hundreds/sec), blocking the compositor and causing freezes.
|
||||||
|
let _scrollTicking = false;
|
||||||
const onScroll = () => {
|
const onScroll = () => {
|
||||||
const currentContainer = postsContainer;
|
if (_scrollTicking) return;
|
||||||
// Only run if THIS container is the active one and still in DOM
|
_scrollTicking = true;
|
||||||
if (!currentContainer || !document.body.contains(currentContainer)) {
|
requestAnimationFrame(() => {
|
||||||
window.removeEventListener('scroll', onScroll);
|
_scrollTicking = false;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (currentContainer.classList.contains('no-infinite-scroll')) return;
|
|
||||||
if (!document.querySelector('#main')) return;
|
|
||||||
|
|
||||||
updateUrlAndPagination();
|
const currentContainer = postsContainer;
|
||||||
|
// Only run if THIS container is the active one and still in DOM
|
||||||
|
if (!currentContainer || !document.body.contains(currentContainer)) {
|
||||||
|
window.removeEventListener('scroll', onScroll);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentContainer.classList.contains('no-infinite-scroll')) return;
|
||||||
|
if (!main) return;
|
||||||
|
|
||||||
const scrollPosition = window.innerHeight + window.scrollY;
|
updateUrlAndPagination();
|
||||||
const pageHeight = document.querySelector('#main').offsetHeight;
|
|
||||||
const distanceFromBottom = pageHeight - scrollPosition;
|
|
||||||
|
|
||||||
// Load more when within PRELOAD_OFFSET pixels of bottom
|
const scrollPosition = window.innerHeight + window.scrollY;
|
||||||
if (distanceFromBottom < PRELOAD_OFFSET && infiniteState.hasMore && !infiniteState.loading) {
|
const pageHeight = main.offsetHeight; // use cached ref, not querySelector every scroll
|
||||||
loadMoreItems();
|
const distanceFromBottom = pageHeight - scrollPosition;
|
||||||
}
|
|
||||||
|
|
||||||
// Load previous when within PRELOAD_OFFSET pixels of top
|
// Load more when within PRELOAD_OFFSET pixels of bottom
|
||||||
// Also ensure we aren't already at page 1
|
if (distanceFromBottom < PRELOAD_OFFSET && infiniteState.hasMore && !infiniteState.loading) {
|
||||||
if (window.scrollY < PRELOAD_OFFSET && infiniteState.hasPrev && !infiniteState.loadingUp) {
|
loadMoreItems();
|
||||||
loadPreviousItems();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
infiniteState.lastScrollY = window.scrollY;
|
// Load previous when within PRELOAD_OFFSET pixels of top
|
||||||
|
if (window.scrollY < PRELOAD_OFFSET && infiniteState.hasPrev && !infiniteState.loadingUp) {
|
||||||
|
loadPreviousItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
infiniteState.lastScrollY = window.scrollY;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Store scroll handler reference for cleanup
|
// Store scroll handler reference for cleanup
|
||||||
postsContainer._scrollHandler = onScroll;
|
postsContainer._scrollHandler = onScroll;
|
||||||
window.addEventListener("scroll", onScroll);
|
window.addEventListener('scroll', onScroll, { passive: true });
|
||||||
|
|
||||||
// Initial check (in case we loaded at top/bottom)
|
// Initial check (in case we loaded at top/bottom)
|
||||||
setTimeout(onScroll, 100);
|
setTimeout(onScroll, 100);
|
||||||
@@ -8275,13 +8279,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const file = thumb.dataset.file;
|
const file = thumb.dataset.file;
|
||||||
const mime = thumb.dataset.mime;
|
const mime = thumb.dataset.mime;
|
||||||
|
|
||||||
// Only preview videos/gifs
|
// Only preview videos (not gifs — fetching a GIF causes main-thread freeze on click)
|
||||||
if (!file || !mime || (!mime.startsWith('video/') && mime !== 'image/gif') || mime === 'video/youtube') return;
|
if (!file || !mime || !mime.startsWith('video/') || mime === 'video/youtube') return;
|
||||||
|
|
||||||
// Helper to actually start video
|
// Helper to actually start video — only called AFTER the hover delay
|
||||||
|
// so we don't start a network fetch during the 150ms grace period.
|
||||||
const run = () => {
|
const run = () => {
|
||||||
if (!document.body.contains(thumb)) return; // Thumb removed
|
if (!document.body.contains(thumb)) return;
|
||||||
if (thumb !== activeThumb) return; // Switched away
|
if (thumb !== activeThumb) return;
|
||||||
|
|
||||||
activeThumb.classList.add('previewing');
|
activeThumb.classList.add('previewing');
|
||||||
|
|
||||||
@@ -8291,10 +8296,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
video.loop = true;
|
video.loop = true;
|
||||||
video.className = 'preview-video';
|
video.className = 'preview-video';
|
||||||
video.playsInline = true;
|
video.playsInline = true;
|
||||||
video.preload = 'auto';
|
// 'metadata' fetches only the file header (a few KB for duration/dimensions).
|
||||||
|
// 'auto' fetches the entire file immediately — catastrophic for large GIFs.
|
||||||
|
video.preload = 'metadata';
|
||||||
|
|
||||||
video.oncanplay = () => {
|
video.oncanplay = () => {
|
||||||
video.play().catch(err => { /* Autoplay blocked */ });
|
video.play().catch(() => {});
|
||||||
video.classList.add('playing');
|
video.classList.add('playing');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user