window.requestAnimFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; })(); (() => { let video; if (elem = document.querySelector("#my-video")) { video = new v0ck(elem); document.addEventListener("keydown", e => { if (e.key === " " && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") { video[video.paused ? 'play' : 'pause'](); document.querySelector('.v0ck_overlay').classList[video.paused ? 'remove' : 'add']('v0ck_hidden'); } }); const toggleBg = document.getElementById('togglebg'); if (toggleBg) { toggleBg.addEventListener('click', function (e) { e.preventDefault(); background = !background; localStorage.setItem('background', background.toString()); var canvas = document.getElementById('bg'); if (background) { canvas.classList.add('fader-in'); canvas.classList.remove('fader-out'); } else { canvas.classList.add('fader-out'); canvas.classList.remove('fader-in'); } animationLoop(); }); } if (elem !== null) { if (localStorage.getItem('background') == undefined) { localStorage.setItem('background', 'true'); } var background = localStorage.getItem('background') === 'true'; var canvas = document.getElementById('bg'); if (canvas) { var context = canvas.getContext('2d'); var cw = canvas.width = canvas.clientWidth | 0; var ch = canvas.height = canvas.clientHeight | 0; function animationLoop() { if (video.paused || video.ended || !background) return; context.drawImage(video, 0, 0, cw, ch); window.requestAnimFrame(animationLoop); } elem.addEventListener('play', animationLoop); } } } let tt = false; const stimeout = 500; const setupMedia = () => { if (elem = document.querySelector("#my-video")) { video = new v0ck(elem); } }; const loadItemAjax = async (url, inheritContext = true) => { console.log("loadItemAjax called with:", url, "inheritContext:", inheritContext); // Show loading indicator const navbar = document.querySelector("nav.navbar"); if (navbar) navbar.classList.add("pbwork"); // Extract item ID from URL. Regex now handles query params, hashes, and trailing slashes. const match = url.match(/\/(\d+)(?:\/|#|\?|$)/); if (!match) { console.warn("loadItemAjax: No ID match found in URL", url); // fallback for weird/external links window.location.href = url; return; } const itemid = match[1]; // // Extract context from Target URL first let tag = null, user = null; const tagMatch = url.match(/\/tag\/([^/]+)/); if (tagMatch) tag = decodeURIComponent(tagMatch[1]); const userMatch = url.match(/\/user\/([^/]+)/); if (userMatch) user = decodeURIComponent(userMatch[1]); // Note: "user" variable shadowed? No, block scope or different name? let user defined above. // If missing and inheritContext is true, check Window Location if (inheritContext) { if (!tag) { const wTagMatch = window.location.href.match(/\/tag\/([^/]+)/); if (wTagMatch) tag = decodeURIComponent(wTagMatch[1]); } if (!user) { const wUserMatch = window.location.href.match(/\/user\/([^/]+)/); if (wUserMatch) user = decodeURIComponent(wUserMatch[1]); } } // try { // Construct AJAX URL let ajaxUrl = `/ajax/item/${itemid}`; const params = new URLSearchParams(); if (tag) params.append('tag', tag); if (user) params.append('user', user); if ([...params].length > 0) { ajaxUrl += '?' + params.toString(); } console.log("Fetching:", ajaxUrl); const response = await fetch(ajaxUrl); if (!response.ok) throw new Error(`Network response was not ok: ${response.status}`); const rawText = await response.text(); let html, paginationHtml; try { // Optimistically try to parse as JSON first const data = JSON.parse(rawText); if (data && typeof data.html === 'string') { html = data.html; paginationHtml = data.pagination; } else { html = rawText; } } catch (e) { // If JSON parse fails, assume it's HTML text html = rawText; } let container = document.querySelector('.container'); if (!container && document.querySelector('.index-container')) { // Transition from Index to Item View const main = document.getElementById('main'); main.innerHTML = '
'; container = main.querySelector('.container'); } else if (container) { // Already in Item View, clear usage const oldContent = container.querySelector('.content'); const oldMetadata = container.querySelector('.metadata'); const oldHeader = container.querySelector('._204863'); if (oldHeader) oldHeader.remove(); if (oldContent) oldContent.remove(); if (oldMetadata) oldMetadata.remove(); } container.insertAdjacentHTML('beforeend', html); // Update pagination if present if (paginationHtml) { const pagWrappers = document.querySelectorAll('.pagination-wrapper'); pagWrappers.forEach(el => el.innerHTML = paginationHtml); } // Construct proper History URL (Context Aware) // If we inherited context, we should reflect it in the URL let pushUrl = `/${itemid}`; // Logic from ajax.mjs context reconstruction: if (user) pushUrl = `/user/${user}/${itemid}`; // User takes precedence usually? Or strictly mutually exclusive in UI else if (tag) pushUrl = `/tag/${tag}/${itemid}`; // We overwrite proper URL even if the link clicked was "naked" history.pushState({}, '', pushUrl); setupMedia(); // Try to extract ID from response if possible or just use itemid document.title = `f0bm - ${itemid}`; if (navbar) navbar.classList.remove("pbwork"); console.log("AJAX load complete"); } catch (err) { console.error("AJAX load failed:", err); } }; const changePage = (e, pbwork = true) => { if (e.tagName === 'A') { e.preventDefault(); loadItemAjax(e.href); } else { pbwork && document.querySelector("nav.navbar").classList.add("pbwork"); !tt && (tt = setTimeout(() => e.click(), stimeout)); } }; // Intercept clicks document.addEventListener('click', (e) => { // Check for thumbnail links on index page const thumbnail = e.target.closest('.posts > a'); if (thumbnail && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) { e.preventDefault(); // Thumbnails inherit context (e.g. from Tag Index) loadItemAjax(thumbnail.href, true); return; } const link = e.target.closest('#next, #prev, #random, .id-link, .nav-next, .nav-prev'); if (link && link.href && link.hostname === window.location.hostname && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) { // Special check for random if (link.id === 'random') { e.preventDefault(); const nav = document.querySelector("nav.navbar"); if (nav) nav.classList.add("pbwork"); // Extract current context from window location let randomUrl = '/api/v2/random'; const params = new URLSearchParams(); const wTagMatch = window.location.href.match(/\/tag\/([^/]+)/); if (wTagMatch) params.append('tag', decodeURIComponent(wTagMatch[1])); const wUserMatch = window.location.href.match(/\/user\/([^/]+)/); if (wUserMatch) params.append('user', decodeURIComponent(wUserMatch[1])); if ([...params].length > 0) { randomUrl += '?' + params.toString(); } fetch(randomUrl) .then(r => r.json()) .then(data => { if (data.success && data.items && data.items.id) { // Inherit context so URL matches current filter loadItemAjax(`/${data.items.id}`, true); } else { window.location.href = link.href; } }) .catch(() => window.location.href = link.href); return; } // Standard item links e.preventDefault(); loadItemAjax(link.href, true); } }); window.addEventListener('popstate', (e) => { loadItemAjax(window.location.href, true); }); // const clickOnElementBinding = selector => () => (elem = document.querySelector(selector)) ? elem.click() : null; const keybindings = { "ArrowLeft": clickOnElementBinding("#next"), "a": clickOnElementBinding("#next"), "ArrowRight": clickOnElementBinding("#prev"), "d": clickOnElementBinding("#prev"), "r": clickOnElementBinding("#random"), " ": clickOnElementBinding("#f0ck-image") }; document.addEventListener("keydown", e => { if (e.key in keybindings && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") { if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return; e.preventDefault(); keybindings[e.key](); } }); // // const imgSize = e => new Promise((res, _) => { const i = new Image(); i.addEventListener('load', function () { res({ width: this.width, height: this.height }); }); i.src = e.src; }); // const wheelEventListener = function (event) { if (event.target.closest('.media-object, .steuerung')) { if (event.deltaY < 0) { const el = document.getElementById('next'); if (el && el.href && !el.href.endsWith('#')) el.click(); } else if (event.deltaY > 0) { const el = document.getElementById('prev'); if (el && el.href && !el.href.endsWith('#')) el.click(); } } }; window.addEventListener('wheel', wheelEventListener); // if (f0ckimage = document.querySelector("img#f0ck-image")) { const f0ckimagescroll = document.querySelector("#image-scroll"); let isImageExpanded = false; console.log("entry point - image unclicked") f0ckimage.addEventListener("click", async e => { e.preventDefault(); const img = await imgSize(f0ckimage); console.log("img clicked"); if (isImageExpanded) { isImageExpanded = false; f0ckimagescroll.removeAttribute("style"); f0ckimage.removeAttribute("style"); console.log("image is not expanded") window.addEventListener('wheel', wheelEventListener); } else { if (img.width > img.height) return; isImageExpanded = true; window.removeEventListener('wheel', wheelEventListener); f0ckimagescroll.setAttribute("style", "overflow-y: scroll"); f0ckimage.setAttribute("style", "max-height: none; height: auto; width: 100%; position: absolute; left: 0; border: var(--img-border-width) solid var(--img-border-color); border-top: none; border-bottom: none;"); } }); } // // let tts = 0; const scroll_treshold = 1; if ([...document.querySelectorAll("div.posts")].length === 1) { document.addEventListener("wheel", e => { if (!document.querySelector('#main')) return; if (Math.ceil(window.innerHeight + window.scrollY) >= document.querySelector('#main').offsetHeight && e.deltaY > 0) { // down if (elem = document.querySelector(".pagination > .next:not(.disabled)")) { if (tts < scroll_treshold) { const foot = document.querySelector("div#footbar"); if (foot) { foot.style.boxShadow = "inset 0px 4px 0px var(--footbar-color)"; foot.style.color = "var(--footbar-color)"; } tts++; } else changePage(elem); } } else if (window.scrollY <= 0 && e.deltaY < 0) { // up if (elem = document.querySelector(".pagination > .prev:not(.disabled)")) { if (tts < scroll_treshold) { const nav = document.querySelector("nav.navbar"); if (nav) { nav.style.boxShadow = "0px 2px 0px var(--loading-indicator-color)"; nav.style.transition = ".2s ease-in-out"; } tts++; } else changePage(elem); } } else { tts = 0; const foot = document.querySelector("div#footbar"); if (foot) { foot.style.boxShadow = "unset"; foot.style.color = "transparent"; } const nav = document.querySelector("nav.navbar"); if (nav) nav.style.boxShadow = "unset"; } }); } const rmatch = /\/p\/(\d+?)/; if (document.referrer.match(rmatch) && document.location.href.match(rmatch)) if (document.location.href.match(rmatch) < document.referrer.match(rmatch)) document.body.scrollTop = document.body.scrollHeight; // // const swipeRT = { xDown: null, yDown: null, xDiff: null, yDiff: null, timeDown: null, startEl: null }; const swipeOpt = { treshold: 20, // 20px timeout: 500 // 500ms }; document.addEventListener('touchstart', e => { swipeRT.startEl = e.target; swipeRT.timeDown = Date.now(); swipeRT.xDown = e.touches[0].clientX; swipeRT.yDown = e.touches[0].clientY; swipeRT.xDiff = 0; swipeRT.yDiff = 0; }, false); document.addEventListener('touchmove', e => { if (!swipeRT.xDown || !swipeRT.yDown) return; swipeRT.xDiff = swipeRT.xDown - e.touches[0].clientX; swipeRT.yDiff = swipeRT.yDown - e.touches[0].clientY; }, false); document.addEventListener('touchend', e => { if (swipeRT.startEl !== e.target) return; const timeDiff = Date.now() - swipeRT.timeDown; let elem; if (Math.abs(swipeRT.xDiff) > Math.abs(swipeRT.yDiff)) { if (Math.abs(swipeRT.xDiff) > swipeOpt.treshold && timeDiff < swipeOpt.timeout) { if (swipeRT.xDiff > 0) // left elem = document.querySelector(".pagination > .next:not(.disabled)"); else // right elem = document.querySelector(".pagination > .prev:not(.disabled)"); } } else { if (Math.abs(swipeRT.yDiff) > swipeOpt.treshold && timeDiff < swipeOpt.timeout) { if (navbar = document.querySelector("nav.navbar") && document.querySelector("div.posts")) { if (swipeRT.yDiff > 0 && (window.innerHeight + window.scrollY) >= document.body.offsetHeight) // up elem = document.querySelector(".pagination > .next:not(.disabled)"); else if (swipeRT.yDiff <= 0 && window.scrollY <= 0 && document.querySelector("div.posts")) // down elem = document.querySelector(".pagination > .prev:not(.disabled)"); } } } swipeRT.xDown = null; swipeRT.yDown = null; swipeRT.timeDown = null; if (elem) changePage(elem); }, false); // // if (audioElement = document.querySelector("audio")) { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = 1920; canvas.height = 1080; setTimeout(() => { document.querySelector(".v0ck").insertAdjacentElement("afterbegin", canvas); }, 400); const audioCtx = new AudioContext(); const analyser = audioCtx.createAnalyser(); analyser.fftSize = 2048; const source = audioCtx.createMediaElementSource(audioElement); source.connect(analyser); source.connect(audioCtx.destination); let data = new Uint8Array(analyser.frequencyBinCount); requestAnimationFrame(loopingFunction); function loopingFunction() { requestAnimationFrame(loopingFunction); analyser.getByteFrequencyData(data); draw(data); } function draw(data) { data = [...data]; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = getComputedStyle(document.body).getPropertyValue("--accent"); data.forEach((value, i) => { const percent = value / 256; const height = (canvas.height * percent / 2) - 40; const offset = canvas.height - height - 1; const barWidth = canvas.width / analyser.frequencyBinCount; ctx.fillRect(i * barWidth, offset, barWidth, height); }); } audioElement.onplay = () => { audioCtx.resume(); }; } // // if (elem = document.querySelector("#my-video") && "mediaSession" in navigator) { const playpauseEvent = () => { video[video.paused ? 'play' : 'pause'](); document.querySelector('.v0ck_overlay').classList[video.paused ? 'remove' : 'add']('v0ck_hidden'); }; navigator.mediaSession.setActionHandler('play', playpauseEvent); navigator.mediaSession.setActionHandler('pause', playpauseEvent); navigator.mediaSession.setActionHandler('stop', playpauseEvent); navigator.mediaSession.setActionHandler('previoustrack', () => { if (link = document.querySelector(".pagination > .prev:not(.disabled)")) changePage(link); }); navigator.mediaSession.setActionHandler('nexttrack', () => { if (link = document.querySelector(".pagination > .next:not(.disabled)")) changePage(link); }); } // // // })(); // disable default scroll event when mouse is on content div // this is useful for items that have a lot of tags for example: 12536 const targetSelector = '.content'; let isMouseOver = true; function isPageScrollable() { return document.documentElement.scrollHeight > document.documentElement.clientHeight; } function onWheel(e) { if (isMouseOver && isPageScrollable()) { e.preventDefault(); } } function init() { const el = document.querySelector(targetSelector); if (!el) return; el.addEventListener('mouseenter', () => isMouseOver = true); el.addEventListener('mouseleave', () => isMouseOver = false); window.addEventListener('wheel', onWheel, { passive: false }); } window.addEventListener('load', init); document.getElementById('sbtForm').addEventListener('submit', (e) => { e.preventDefault(); const input = document.getElementById('sbtInput').value.trim(); if (input) { window.location.href = `/tag/${encodeURIComponent(input)}`; } });