const PAGE_SIZE = 12; const MAX_FILE_BYTES = 5 * 1024 * 1024; const MAX_IMAGE_PIXELS = 20_000_000; const VIEW_DWELL_MS = 1000; const VIEW_THRESHOLD = 0.9; const VIEW_DEDUPE_MS = 24 * 60 * 60 * 1000; const VIEW_DEDUPE_PREFIX = 'meme-viewed:'; const NEXT_PAGE_LOADER_MIN_MS = 300; const SAFE_TYPES = new Set(['image/png', 'image/jpeg']); const grid = document.querySelector('#meme-grid'); const feedLoader = document.querySelector('#feed-loader'); const feedLoaderLabel = document.querySelector('#feed-loader-label'); const uploadModal = document.querySelector('#upload-modal'); const uploadForm = document.querySelector('#upload-form'); const fileInput = document.querySelector('#meme-input'); const fileLabel = document.querySelector('#file-label'); const formStatus = document.querySelector('#form-status'); const addMemeButton = document.querySelector('#add-meme-button'); const lightbox = document.querySelector('#lightbox'); const lightboxImage = document.querySelector('#lightbox-image'); const lightboxCopy = document.querySelector('#lightbox-copy'); const lightboxDownload = document.querySelector('#lightbox-download'); const lightboxShare = document.querySelector('#lightbox-share'); const lightboxId = document.querySelector('#lightbox-id'); const lightboxViewCount = document.querySelector('#lightbox-view-count'); const lightboxDownloadCount = document.querySelector('#lightbox-download-count'); const petModal = document.querySelector('#pet-modal'); const loreModal = document.querySelector('#lore-modal'); const loreText = document.querySelector('#lore-text'); const loreCopy = document.querySelector('#lore-copy'); const loreShare = document.querySelector('#lore-share'); const shareModal = document.querySelector('#share-modal'); const shareTitle = document.querySelector('#share-title'); const shareLinkLabel = document.querySelector('#share-link-label'); const shareLink = document.querySelector('#share-link'); const shareStatus = document.querySelector('#share-status'); const copyShareLinkButton = document.querySelector('#copy-share-link'); const scrollIndicator = document.querySelector('#scroll-indicator'); const actionStatus = document.querySelector('#action-status'); let currentPage = 0; let totalPages = 1; let isLoading = false; let latestValidationRun = 0; let activeShareTrigger = null; let shareStatusTimer = null; let actionStatusTimer = null; let loreContent = ''; const viewedThisSession = new Set(); const pendingViewTimers = new Map(); const copiedFeedbackTimers = new WeakMap(); document.querySelector('#open-upload').addEventListener('click', () => { formStatus.textContent = ''; uploadModal.showModal(); }); document.querySelector('#close-upload').addEventListener('click', closeUpload); document.querySelector('#cancel-upload').addEventListener('click', closeUpload); document.querySelector('#close-lightbox').addEventListener('click', () => lightbox.close()); document.querySelector('#close-share').addEventListener('click', () => shareModal.close()); document.querySelector('#open-pet').addEventListener('click', () => petModal.showModal()); document.querySelector('#close-pet').addEventListener('click', () => petModal.close()); document.querySelector('#open-lore').addEventListener('click', openLoreDialog); document.querySelector('#close-lore').addEventListener('click', () => loreModal.close()); copyShareLinkButton.addEventListener('click', copyActiveShareLink); lightboxShare.addEventListener('click', async () => { await openMemeShareDialog(lightboxShare.dataset.shareId, lightboxShare); }); lightboxCopy.addEventListener('click', () => copyImage(lightboxCopy.dataset.imageUrl, lightboxCopy)); loreCopy.addEventListener('click', copyLore); loreShare.addEventListener('click', () => openShareDialog({ path: '/lore', title: 'SHARE_LORE', label: 'LORE_LINK', triggerButton: loreShare })); fileInput.addEventListener('change', async () => { const validationRun = ++latestValidationRun; const file = fileInput.files?.[0]; fileLabel.textContent = file ? file.name : 'SELECT PNG / JPEG'; formStatus.textContent = file ? 'INSPECTING_IMAGE...' : ''; const validationError = await validateClientFile(file); if (validationRun === latestValidationRun) { formStatus.textContent = validationError || ''; } }); uploadForm.addEventListener('submit', async (event) => { event.preventDefault(); const file = fileInput.files?.[0]; const validationError = await validateClientFile(file); if (validationError) { formStatus.textContent = validationError; return; } addMemeButton.disabled = true; formStatus.textContent = 'UPLOADING...'; try { const formData = new FormData(); formData.append('meme', file); const response = await fetch('/api/memes', { method: 'POST', body: formData }); const payload = await response.json(); if (!response.ok) throw new Error(payload.error || 'Upload failed.'); if (payload.meme?.status === 'approved') { closeUpload(); await loadMemes(1, { reset: true }); } else { uploadForm.reset(); fileLabel.textContent = 'SELECT PNG / JPEG'; formStatus.textContent = payload.message || 'Upload queued for admin review.'; } } catch (error) { formStatus.textContent = error.message.toUpperCase(); } finally { addMemeButton.disabled = false; } }); grid.addEventListener('click', async (event) => { const copyButton = event.target.closest('[data-copy-image-url]'); if (copyButton) { await copyImage(copyButton.dataset.copyImageUrl, copyButton); return; } const shareButton = event.target.closest('[data-share-id]'); if (shareButton) { await openMemeShareDialog(shareButton.dataset.shareId, shareButton); return; } const viewButton = event.target.closest('[data-view-id]'); if (!viewButton) return; const card = viewButton.closest('.meme-card'); await openLightbox({ id: viewButton.dataset.viewId, url: viewButton.dataset.viewUrl, downloadUrl: viewButton.dataset.downloadUrl, alt: card.querySelector('img').alt, viewCount: Number.parseInt(viewButton.dataset.viewCount || '0', 10), downloadCount: Number.parseInt(viewButton.dataset.downloadCount || '0', 10) }); }); const observer = new IntersectionObserver((entries) => { if (entries.some((entry) => entry.isIntersecting)) { loadNextPage(); } }, { rootMargin: '420px 0px' }); const viewObserver = 'IntersectionObserver' in window ? new IntersectionObserver(handleViewIntersections, { threshold: [VIEW_THRESHOLD] }) : null; connectLiveCounters(); updateScrollIndicator(); window.addEventListener('scroll', updateScrollIndicator, { passive: true }); window.addEventListener('resize', updateScrollIndicator); refreshStatus(); window.setInterval(refreshStatus, 5000); await openSharedContentFromUrl(); await loadMemes(1, { reset: true }); observer.observe(feedLoader); async function loadNextPage() { if (isLoading || currentPage >= totalPages) return; await loadMemes(currentPage + 1); } async function loadMemes(page, options = {}) { if (isLoading) return; isLoading = true; const loaderStartedAt = performance.now(); setLoader('FETCHING_NEXT_BLOCK...', true); try { const response = await fetch(`/api/memes?page=${page}&pageSize=${PAGE_SIZE}`); const payload = await response.json(); if (!response.ok) throw new Error(payload.error || 'Feed error.'); currentPage = payload.page; totalPages = payload.totalPages; renderMemes(payload.memes, { append: !options.reset }); if (page > 1) { const remainingLoaderTime = NEXT_PAGE_LOADER_MIN_MS - (performance.now() - loaderStartedAt); if (remainingLoaderTime > 0) await delay(remainingLoaderTime); } setLoader(currentPage < totalPages ? 'SCROLL_FOR_NEXT_BLOCK' : 'STREAM_SYNCHRONIZED', false); updateScrollIndicator(); } catch { if (options.reset) grid.innerHTML = `
FEED_ERROR
`; setLoader('FEED_ERROR', false); } finally { grid.ariaBusy = 'false'; isLoading = false; } } function delay(milliseconds) { return new Promise((resolve) => window.setTimeout(resolve, milliseconds)); } function renderMemes(memes, options = {}) { if (!options.append && memes.length === 0) { unobserveViewedImages(); grid.innerHTML = `
NO_MEMES_IN_STREAM
`; return; } const cards = memes.map((meme, index) => { const article = document.createElement('article'); article.className = 'meme-card'; article.dataset.memeId = meme.id; article.innerHTML = `
ID: ${shortId(meme.id)}
${relativeAge(meme.createdAt)}
Uploaded meme ${shortId(meme.id)}
${formatCount(meme.viewCount)} ${formatCount(meme.downloadCount)}
`; if (options.append && index === 0) article.tabIndex = -1; return article; }); if (options.append) { grid.append(...cards); } else { unobserveViewedImages(); grid.replaceChildren(...cards); } observeViewedImages(cards); } async function openSharedContentFromUrl() { if (window.location.pathname === '/lore' || window.location.pathname === '/lore/') { await openLoreDialog(); return; } const match = window.location.pathname.match(/^\/meme\/([a-f0-9]{64})\/?$/); if (!match) return; try { const response = await fetch(`/api/memes/${match[1]}`); const payload = await response.json(); if (!response.ok) throw new Error(payload.error || 'Meme not found.'); await openLightbox({ ...payload.meme, alt: `Shared meme ${shortId(payload.meme.id)}` }); } catch { window.location.replace('/'); } } async function openLightbox({ id, url, downloadUrl, alt, viewCount = 0, downloadCount = 0 }) { lightboxImage.src = url; lightboxImage.alt = alt; lightboxCopy.dataset.imageUrl = url; lightboxDownload.href = downloadUrl; lightboxShare.dataset.shareId = id; lightboxViewCount.dataset.counterId = id; lightboxViewCount.dataset.counterKind = 'view'; lightboxViewCount.querySelector('[data-count]').textContent = formatCount(viewCount); lightboxDownloadCount.dataset.counterId = id; lightboxDownloadCount.dataset.counterKind = 'download'; lightboxDownloadCount.querySelector('[data-count]').textContent = formatCount(downloadCount); lightboxId.textContent = `ID: 0x${id.toUpperCase()}`; setActionStatus('', lightboxCopy); if (!lightbox.open) lightbox.showModal(); await recordViewOnce(id); } async function openMemeShareDialog(id, triggerButton = null) { if (!isMemeId(id)) return; return openShareDialog({ path: `/meme/${id}`, title: 'SHARE_MEME', label: 'MEME_LINK', triggerButton }); } async function openShareDialog({ path, title, label, triggerButton = null }) { activeShareTrigger = triggerButton; shareTitle.textContent = title; shareLinkLabel.textContent = label; shareLink.value = new URL(path, window.location.origin).href; window.clearTimeout(shareStatusTimer); shareStatusTimer = null; shareStatus.textContent = ''; resetCopiedFeedback(copyShareLinkButton); if (!shareModal.open) shareModal.showModal(); } async function copyActiveShareLink() { if (!shareLink.value) return; copyShareLinkButton.disabled = true; try { await copyText(shareLink.value); shareStatus.textContent = 'LINK_COPIED_TO_CLIPBOARD'; showCopiedFeedback(copyShareLinkButton, 'Share link copied'); if (activeShareTrigger) showCopiedFeedback(activeShareTrigger, 'Share link copied'); window.clearTimeout(shareStatusTimer); shareStatusTimer = window.setTimeout(() => { shareStatus.textContent = ''; shareStatusTimer = null; }, 2600); } catch { shareLink.focus(); shareLink.select(); shareStatus.textContent = 'COPY_UNAVAILABLE — SELECT_AND_COPY_THE_LINK'; } finally { copyShareLinkButton.disabled = false; } } async function openLoreDialog() { setActionStatus('', loreCopy); if (!loreModal.open) loreModal.showModal(); if (loreContent) return; try { const response = await fetch('/assets/meme-protocol-lore.txt'); if (!response.ok) throw new Error('Lore unavailable.'); loreContent = await response.text(); loreText.textContent = loreContent; } catch { loreText.textContent = 'LORE_TRANSMISSION_UNAVAILABLE'; setActionStatus('LORE_TRANSMISSION_UNAVAILABLE', loreCopy); } } async function copyLore() { if (!loreContent) await openLoreDialog(); if (!loreContent) return; loreCopy.disabled = true; try { await copyText(loreContent); showCopiedFeedback(loreCopy, 'Lore copied'); setActionStatus('LORE_COPIED_TO_CLIPBOARD', loreCopy); } catch { setActionStatus('LORE_COPY_UNAVAILABLE — USE_DOWNLOAD', loreCopy); } finally { loreCopy.disabled = false; } } async function copyImage(url, button) { if (!url || !button) return; button.disabled = true; setActionStatus('', button); try { if (!navigator.clipboard?.write || typeof ClipboardItem === 'undefined') { throw new Error('Image clipboard unavailable.'); } const png = fetch(url) .then((response) => { if (!response.ok) throw new Error('Image fetch failed.'); return response.blob(); }) .then(convertImageBlobToPng); await navigator.clipboard.write([new ClipboardItem({ 'image/png': png })]); showCopiedFeedback(button, 'Meme image copied'); setActionStatus('MEME_IMAGE_COPIED_TO_CLIPBOARD', button); } catch { setActionStatus('IMAGE_COPY_UNAVAILABLE — USE_DOWNLOAD', button); } finally { button.disabled = false; } } async function convertImageBlobToPng(blob) { const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); if (!context) throw new Error('Canvas unavailable.'); let image; let cleanup = () => {}; if ('createImageBitmap' in window) { image = await createImageBitmap(blob); cleanup = () => image.close(); } else { const objectUrl = URL.createObjectURL(blob); image = await loadImage(objectUrl); cleanup = () => URL.revokeObjectURL(objectUrl); } try { canvas.width = image.width; canvas.height = image.height; context.drawImage(image, 0, 0); return await new Promise((resolve, reject) => { canvas.toBlob((png) => { if (png) resolve(png); else reject(new Error('PNG conversion failed.')); }, 'image/png'); }); } finally { cleanup(); } } function loadImage(url) { return new Promise((resolve, reject) => { const image = new Image(); image.onload = () => resolve(image); image.onerror = () => reject(new Error('Image decode failed.')); image.src = url; }); } function showCopiedFeedback(button, copiedLabel = 'Copied') { const icon = button.querySelector('.material-symbols-outlined'); const label = button.querySelector('[data-copy-label]'); if (!icon) return; button.dataset.defaultIcon ||= icon.textContent.trim(); if (label) button.dataset.defaultLabel ||= label.textContent.trim(); button.dataset.defaultAriaLabel ||= button.getAttribute('aria-label') || ''; button.dataset.defaultTitle ||= button.getAttribute('title') || ''; icon.textContent = 'check'; if (label) label.textContent = 'COPIED'; if (button.dataset.defaultAriaLabel) button.setAttribute('aria-label', copiedLabel); if (button.dataset.defaultTitle) button.setAttribute('title', copiedLabel); button.classList.add('is-copied'); window.clearTimeout(copiedFeedbackTimers.get(button)); copiedFeedbackTimers.set(button, window.setTimeout(() => { resetCopiedFeedback(button); }, 2600)); } function resetCopiedFeedback(button) { const icon = button.querySelector('.material-symbols-outlined'); const label = button.querySelector('[data-copy-label]'); window.clearTimeout(copiedFeedbackTimers.get(button)); copiedFeedbackTimers.delete(button); if (icon && button.dataset.defaultIcon) icon.textContent = button.dataset.defaultIcon; if (label && button.dataset.defaultLabel) label.textContent = button.dataset.defaultLabel; if (button.dataset.defaultAriaLabel) button.setAttribute('aria-label', button.dataset.defaultAriaLabel); if (button.dataset.defaultTitle) button.setAttribute('title', button.dataset.defaultTitle); button.classList.remove('is-copied'); } async function copyText(value) { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(value); return; } const fallback = document.createElement('textarea'); fallback.value = value; fallback.setAttribute('readonly', ''); fallback.className = 'clipboard-fallback'; document.body.append(fallback); fallback.select(); const copied = document.execCommand('copy'); fallback.remove(); if (!copied) throw new Error('Clipboard unavailable.'); } function setActionStatus(message, triggerButton = null) { window.clearTimeout(actionStatusTimer); for (const status of document.querySelectorAll('[data-action-status], #action-status')) { status.textContent = ''; } const target = triggerButton?.closest('dialog')?.querySelector('[data-action-status]') || actionStatus; target.textContent = message; if (!message) return; actionStatusTimer = window.setTimeout(() => { target.textContent = ''; actionStatusTimer = null; }, 3200); } async function validateClientFile(file) { if (!file) return 'SELECT A FILE.'; if (!SAFE_TYPES.has(file.type)) return 'ONLY PNG AND JPEG ARE ACCEPTED.'; if (file.size > MAX_FILE_BYTES) return 'IMAGE EXCEEDS 5MB.'; try { const dimensions = await readImageDimensions(file); if (dimensions.width > 6000 || dimensions.height > 6000) return 'IMAGE EDGE EXCEEDS 6000PX.'; if (dimensions.width * dimensions.height > MAX_IMAGE_PIXELS) return 'IMAGE EXCEEDS 20MP.'; } catch { return 'IMAGE COULD NOT BE INSPECTED.'; } return ''; } function readImageDimensions(file) { return new Promise((resolve, reject) => { const image = new Image(); const url = URL.createObjectURL(file); image.onload = () => { URL.revokeObjectURL(url); resolve({ width: image.naturalWidth, height: image.naturalHeight }); }; image.onerror = () => { URL.revokeObjectURL(url); reject(new Error('invalid image')); }; image.src = url; }); } function closeUpload() { uploadForm.reset(); fileLabel.textContent = 'SELECT PNG / JPEG'; formStatus.textContent = ''; uploadModal.close(); } function handleViewIntersections(entries) { for (const entry of entries) { const id = entry.target.dataset.viewObserveId; if (!id || wasRecentlyViewed(id)) { stopViewTimer(id); if (wasRecentlyViewed(id)) viewObserver.unobserve(entry.target); continue; } if (entry.isIntersecting && entry.intersectionRatio >= VIEW_THRESHOLD) { startViewTimer(id, entry.target); } else { stopViewTimer(id); } } } function observeViewedImages(cards) { if (!viewObserver) return; for (const card of cards) { const image = card.querySelector('[data-view-observe-id]'); if (image && !wasRecentlyViewed(image.dataset.viewObserveId)) viewObserver.observe(image); } } function unobserveViewedImages() { for (const timer of pendingViewTimers.values()) window.clearTimeout(timer); pendingViewTimers.clear(); if (!viewObserver) return; for (const image of grid.querySelectorAll('[data-view-observe-id]')) { viewObserver.unobserve(image); } } function startViewTimer(id, target) { if (pendingViewTimers.has(id)) return; const timer = window.setTimeout(async () => { pendingViewTimers.delete(id); if (wasRecentlyViewed(id)) { viewObserver.unobserve(target); return; } await recordViewOnce(id); viewObserver.unobserve(target); }, VIEW_DWELL_MS); pendingViewTimers.set(id, timer); } function stopViewTimer(id) { const timer = pendingViewTimers.get(id); if (!timer) return; window.clearTimeout(timer); pendingViewTimers.delete(id); } async function recordViewOnce(id) { if (!isMemeId(id)) return; if (wasRecentlyViewed(id)) return; rememberViewed(id); try { const response = await fetch(`/api/memes/${id}/view`, { method: 'POST' }); const payload = await response.json(); if (!response.ok) throw new Error(payload.error || 'View update failed.'); updateCounters(payload.meme); } catch { forgetViewed(id); // Live counter updates are best-effort; the stream and lightbox should still work. } } function isMemeId(id) { return typeof id === 'string' && /^[a-f0-9]{64}$/.test(id); } function wasRecentlyViewed(id) { if (viewedThisSession.has(id)) return true; try { const viewedAt = Number.parseInt(localStorage.getItem(`${VIEW_DEDUPE_PREFIX}${id}`) || '0', 10); if (Number.isFinite(viewedAt) && Date.now() - viewedAt < VIEW_DEDUPE_MS) return true; } catch { // Storage can be unavailable in hardened browser modes; session memory still dedupes. } return false; } function rememberViewed(id) { viewedThisSession.add(id); try { localStorage.setItem(`${VIEW_DEDUPE_PREFIX}${id}`, String(Date.now())); } catch { // localStorage is optional; no upload or view flow depends on it. } } function forgetViewed(id) { viewedThisSession.delete(id); try { localStorage.removeItem(`${VIEW_DEDUPE_PREFIX}${id}`); } catch { // Optional storage cleanup. } } function connectLiveCounters() { if (!('EventSource' in window)) return; const source = new EventSource('/api/events'); source.addEventListener('metric', (event) => { updateCounters(JSON.parse(event.data)); }); } function updateCounters(meme) { for (const element of document.querySelectorAll(`[data-counter-id="${meme.id}"]`)) { const count = element.querySelector('[data-count]'); if (!count) continue; if (element.dataset.counterKind === 'view') { count.textContent = formatCount(meme.viewCount); } if (element.dataset.counterKind === 'download') { count.textContent = formatCount(meme.downloadCount); } } for (const button of document.querySelectorAll(`[data-view-id="${meme.id}"]`)) { button.dataset.viewCount = String(meme.viewCount); button.dataset.downloadCount = String(meme.downloadCount); } } function setLoader(label, spinning) { feedLoaderLabel.textContent = label; feedLoader.classList.toggle('is-spinning', spinning); } function shortId(id) { return `0x${id.slice(0, 4).toUpperCase()}...${id.slice(-4).toUpperCase()}`; } function relativeAge(value) { const elapsed = Math.max(1, Date.now() - new Date(value).getTime()); const minutes = Math.floor(elapsed / 60000); if (minutes < 60) return `${minutes}M AGO`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}H AGO`; return `${Math.floor(hours / 24)}D AGO`; } function formatCount(value) { const count = Number.isFinite(value) ? value : 0; if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M`; if (count >= 1_000) return `${(count / 1_000).toFixed(1)}K`; return String(count); } function scoreBucket(value) { const score = Number.isFinite(value) ? value : 50; if (score >= 90) return 5; if (score >= 75) return 4; if (score >= 60) return 3; if (score >= 40) return 2; if (score >= 20) return 1; return 0; } async function refreshStatus() { const started = performance.now(); try { const response = await fetch('/api/status', { cache: 'no-store' }); const status = await response.json(); if (!response.ok) throw new Error('status failed'); setStat('status', `${status.ok ? 'ONLINE' : 'DEGRADED'} ${formatUptime(status.uptimeSeconds)}`); setStat('latency', `${Math.max(1, Math.round(performance.now() - started))}MS`); setStat('nodes', formatCount(status.liveClients)); setStat('memes', formatCount(status.memeCount)); } catch { setStat('status', 'OFFLINE'); setStat('latency', '--MS'); setStat('nodes', '--'); setStat('memes', '--'); } } function setStat(name, value) { for (const element of document.querySelectorAll(`[data-stat="${name}"]`)) { element.textContent = value; } } function formatUptime(value) { const seconds = Math.max(0, Number.isFinite(value) ? value : 0); const minutes = Math.floor(seconds / 60); if (minutes < 1) return `UP ${seconds}S`; if (minutes < 60) return `UP ${minutes}M`; const hours = Math.floor(minutes / 60); const remainingMinutes = minutes % 60; if (hours < 24) return `UP ${hours}H${remainingMinutes > 0 ? ` ${remainingMinutes}M` : ''}`; const days = Math.floor(hours / 24); const remainingHours = hours % 24; return `UP ${days}D${remainingHours > 0 ? ` ${remainingHours}H` : ''}`; } function updateScrollIndicator() { const segments = [...scrollIndicator.querySelectorAll('span')]; const maxScroll = Math.max(1, document.documentElement.scrollHeight - window.innerHeight); const progress = Math.min(1, Math.max(0, window.scrollY / maxScroll)); const activeIndex = Math.min(segments.length - 1, Math.round(progress * (segments.length - 1))); segments.forEach((segment, index) => { segment.classList.toggle('active', index === activeIndex); }); }