Unlocks hidden videos and adds a premium gallery.
// ==UserScript==
// @name LPSG Video Unlocker
// @namespace CurlyWurly
// @version 5.4.7
// @description Unlocks hidden videos and adds a premium gallery.
// @author CurlyWurly
// @match https://www.lpsg.com/*
// @icon data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pjxzdmcgdmlld0JveD0iMCAwIDI1NiAyNTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3QgZmlsbD0ibm9uZSIgaGVpZ2h0PSIyNTYiIHdpZHRoPSIyNTYiLz48cGF0aCBkPSJNOTMuMiwxMjIuOEE3MC4zLDcwLjMsMCwwLDEsODgsOTZhNzIsNzIsMCwxLEsNzIsNzIsNzAuMyw3MC4zLDAsMCwxLTI2LjgtNS4yaDBMMTIwLDE3Nkg5NnYyNEg3MnYyMEgzMlYxODRsNjEuMi02MS4yWiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjRkZENzAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMTYiLz48Y2lyY2xlIGN4PSIxODAiIGN5PSI3NiIgcj0iMTIiIGZpbGw9IiNGRkQ3MDAiLz48L3N2Zz4=
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) ||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
const config = {
volume: 0.1,
formats: ['mp4', 'm4v', 'mov'],
glassBase: 'rgba(20, 20, 20, 0.85)',
glassBorder: '1px solid rgba(255, 255, 255, 0.1)',
highlight: '#FFD700',
maxRetries: 3,
retryDelay: 1000
};
const styles = `
:root {
--pg-glass: ${config.glassBase};
--pg-border: ${config.glassBorder};
--pg-highlight: ${config.highlight};
--pg-text: #fff;
}
.p-navgroup-link--iconic--media {
color: inherit;
}
#pg-container {
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
background: rgba(0,0,0,0.92);
backdrop-filter: blur(15px);
z-index: 99999;
display: flex; flex-direction: column;
opacity: 0; transition: opacity 0.3s ease;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
overscroll-behavior: none;
}
#pg-container.pg-visible { opacity: 1; }
.pg-header {
padding: 15px 20px;
display: flex; justify-content: space-between; align-items: center;
background: rgba(0,0,0,0.4);
border-bottom: var(--pg-border);
flex-shrink: 0;
}
.pg-filters { display: flex; gap: 10px; }
.pg-filter-btn {
background: transparent; border: var(--pg-border); color: #aaa;
padding: 6px 16px; border-radius: 20px; cursor: pointer;
font-size: 13px; transition: all 0.2s;
}
.pg-filter-btn.active, .pg-filter-btn:hover {
background: rgba(255, 215, 0, 0.15); border-color: var(--pg-highlight); color: #fff;
}
.pg-close {
background: none; border: none; color: #fff; font-size: 24px; cursor: pointer;
}
.pg-gallery-scroll {
flex-grow: 1; overflow-y: auto; padding: 20px;
scrollbar-width: thin; scrollbar-color: #444 transparent;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
.pg-grid {
column-count: 2; column-gap: 15px;
width: 100%; max-width: 1600px; margin: 0 auto;
}
@media (min-width: 768px) { .pg-grid { column-count: 3; } }
@media (min-width: 1200px) { .pg-grid { column-count: 4; } }
@media (min-width: 1600px) { .pg-grid { column-count: 5; } }
.pg-item {
break-inside: avoid; margin-bottom: 15px;
position: relative; border-radius: 8px; overflow: hidden;
border: var(--pg-border); background: #111;
cursor: pointer; transition: transform 0.2s;
}
.pg-item:hover { transform: scale(1.02); z-index: 2; box-shadow: 0 5px 15px rgba(0,0,0,0.5); }
.pg-item img, .pg-item video {
width: 100%; height: auto; display: block; object-fit: cover;
}
.pg-preview-video {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
object-fit: cover; z-index: 1;
pointer-events: none;
}
.pg-badge {
position: absolute; top: 8px; right: 8px;
background: rgba(0,0,0,0.7); color: #fff;
padding: 3px 8px; border-radius: 4px; font-size: 10px;
font-weight: bold; pointer-events: none;
backdrop-filter: blur(4px); z-index: 5;
}
.pg-badge.video { color: var(--pg-highlight); }
.pg-status-overlay {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(20, 20, 20, 0.85);
display: flex; flex-direction: column;
align-items: center; justify-content: center;
z-index: 4;
backdrop-filter: blur(2px);
opacity: 1; transition: opacity 0.3s;
}
.pg-status-text {
color: #ccc; font-size: 12px; margin-top: 5px;
text-align: center;
}
.pg-status-icon { font-size: 20px; }
.pg-status-retry { color: var(--pg-highlight); animation: pg-pulse 1.5s infinite; }
.pg-status-broken { color: #ff5555; }
@keyframes pg-pulse { 0% { opacity: 0.6; } 50% { opacity: 1; } 100% { opacity: 0.6; } }
.pg-lazy-img {
opacity: 0;
transition: opacity 0.4s ease-in-out;
}
.pg-lazy-img.pg-loaded {
opacity: 1;
}
#pg-lightbox {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.95);
display: flex; flex-direction: column;
z-index: 100000; opacity: 0; pointer-events: none; transition: opacity 0.3s;
}
#pg-lightbox.active { opacity: 1; pointer-events: auto; }
.pg-lb-content {
flex-grow: 1; display: flex; align-items: center; justify-content: center;
overflow: hidden; padding: 20px; position: relative;
}
.pg-lb-media {
max-width: 100%; max-height: 100%;
object-fit: contain; box-shadow: 0 0 30px rgba(0,0,0,0.5);
border-radius: 4px;
}
.pg-lb-header {
position: absolute; top: 0; left: 0; right: 0;
padding: 15px; display: flex; justify-content: center;
z-index: 10; background: linear-gradient(to bottom, rgba(0,0,0,0.7), transparent);
}
.pg-lb-counter { font-size: 14px; color: #fff; background: rgba(0,0,0,0.4); padding: 4px 12px; border-radius: 20px; }
.pg-lb-controls {
position: absolute; bottom: 30px; left: 50%; transform: translateX(-50%);
display: flex; gap: 20px; align-items: center;
background: rgba(30,30,30,0.8); padding: 10px 25px; border-radius: 30px;
border: var(--pg-border); backdrop-filter: blur(10px);
z-index: 10;
opacity: 0.2;
transition: opacity 0.3s ease, background 0.3s;
}
.pg-lb-controls:hover { opacity: 1; background: rgba(30,30,30,0.95); }
.pg-ctrl-btn {
background: none; border: none; color: #fff; font-size: 20px; cursor: pointer;
padding: 5px 10px; transition: color 0.2s;
display: flex; align-items: center; justify-content: center;
}
.pg-ctrl-btn:hover { color: var(--pg-highlight); }
.pg-ctrl-btn:disabled { color: #444; cursor: default; }
@keyframes pg-play-appear {
from { opacity: 0; transform: translate(-50%, -50%) scale(0.7); }
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
.pg-ios-play-btn {
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 80px; height: 80px;
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
border: 1.5px solid rgba(255, 255, 255, 0.2);
cursor: pointer;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1),
background 0.25s ease,
box-shadow 0.25s ease;
animation: pg-play-appear 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.pg-ios-play-btn:hover {
transform: translate(-50%, -50%) scale(1.12);
background: rgba(255, 255, 255, 0.22);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
}
.pg-ios-play-btn:active {
transform: translate(-50%, -50%) scale(0.95);
}
.pg-ios-play-btn svg {
filter: drop-shadow(0 1px 3px rgba(0,0,0,0.3));
margin-left: 3px;
}
.pg-loader {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
width: 30px; height: 30px; border: 3px solid #333; border-top-color: var(--pg-highlight);
border-radius: 50%; animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: translate(-50%, -50%) rotate(360deg); } }
`;
const styleSheet = document.createElement("style");
styleSheet.innerText = styles;
document.head.appendChild(styleSheet);
// --- SVG Icons ---
const gridIconSvg = `<svg viewBox="0 0 24 24" width="22" height="22" stroke="white" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>`;
const warnIconSvg = `<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>`;
const brokenIconSvg = `<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>`;
// --- Unlock Logic ---
function unlockVideos() {
const easterEggPoster = document.getElementsByClassName("video-easter-egg-poster");
[...easterEggPoster].forEach(poster => {
const img = poster.querySelector('img');
if (!img) return;
const imageUrl = img.src;
const sourceElements = config.formats.map(format => {
let videoUrl = imageUrl.replace("attachments/posters", "video")
.replace("/lsvideo/thumbnails", "lsvideo/videos")
.replace(/\.(jpg|jpeg|png)$/i, `.${format}`);
let mimeType = format === 'mp4' ? 'video/mp4' : (format === 'mov' ? 'video/quicktime' : 'video/x-m4v');
return `<source src="${videoUrl}" type="${mimeType}">`;
}).join('');
const videoHTML = `
<div class="newVideoDiv" style="display: inline-block; max-width: 100%;">
<video class="message-cell--main-video" controls playsinline preload="metadata"
poster="${imageUrl}" style="max-width: 100%; width: auto; max-height: 80vh; display: block; margin: 0 auto;">
${sourceElements}
<div class="bbMediaWrapper-fallback">Video format not supported.</div>
</video>
</div>`;
poster.insertAdjacentHTML('afterend', videoHTML);
poster.remove();
});
document.querySelectorAll('.video-easter-egg-blocker, .video-easter-egg-overlay').forEach(el => el.remove());
document.querySelectorAll('video').forEach(v => {
if (!v.dataset.volSet) {
v.volume = config.volume;
v.dataset.volSet = "true";
}
});
document.querySelectorAll('img[loading="lazy"]').forEach(img => {
img.loading = 'eager';
if (img.dataset.src) img.src = img.dataset.src;
});
}
// --- State Management ---
let galleryState = {
media: [],
filteredMedia: [],
currentIndex: 0,
filter: 'all',
container: null,
lightbox: null
};
// --- Scroll Lock ---
let savedScrollY = 0;
function lockBodyScroll() {
savedScrollY = window.scrollY || document.documentElement.scrollTop || 0;
document.body.style.position = 'fixed';
document.body.style.top = `-${savedScrollY}px`;
document.body.style.left = '0';
document.body.style.right = '0';
document.body.style.width = '100%';
document.body.style.overflow = 'hidden';
}
function unlockBodyScroll() {
document.body.style.position = '';
document.body.style.top = '';
document.body.style.left = '';
document.body.style.right = '';
document.body.style.width = '';
document.body.style.overflow = '';
window.scrollTo(0, savedScrollY);
}
// --- Sequential Retry Logic ---
const retryQueue = [];
let isRetrying = false;
function queueRetry(imgElement, fullSrc, attemptNum) {
retryQueue.push({ img: imgElement, src: fullSrc, attempt: attemptNum });
if (!isRetrying) {
processRetryQueue();
}
}
function processRetryQueue() {
if (retryQueue.length === 0) {
isRetrying = false;
return;
}
isRetrying = true;
const item = retryQueue.shift();
const dynamicDelay = config.retryDelay * item.attempt;
setTimeout(() => {
const separator = item.src.includes('?') ? '&' : '?';
const timestamp = Date.now();
item.img.src = `${item.src}${separator}retry=${timestamp}`;
processRetryQueue();
}, dynamicDelay);
}
function setStatusOverlay(cardElement, type, text) {
let overlay = cardElement.querySelector('.pg-status-overlay');
if (!overlay) {
overlay = document.createElement('div');
overlay.className = 'pg-status-overlay';
cardElement.appendChild(overlay);
}
if (type === 'clear') {
overlay.remove();
return;
}
const iconHtml = type === 'retry'
? `<div class="pg-status-icon pg-status-retry">${warnIconSvg}</div>`
: `<div class="pg-status-icon pg-status-broken">${brokenIconSvg}</div>`;
const txtClass = type === 'broken' ? 'pg-status-broken' : 'pg-status-retry';
overlay.innerHTML = `
${iconHtml}
<div class="pg-status-text ${txtClass}">${text}</div>
`;
}
// --- Lazy Loading ---
let lazyImageObserver;
function initLazyObserver() {
if (lazyImageObserver) lazyImageObserver.disconnect();
const options = {
root: document.querySelector('.pg-gallery-scroll'),
rootMargin: '200px',
threshold: 0.01
};
lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const src = img.dataset.src;
if (src) {
img.src = src;
observer.unobserve(img);
}
}
});
}, options);
document.querySelectorAll('.pg-lazy-img').forEach(img => {
lazyImageObserver.observe(img);
});
}
function scanMedia() {
const videos = Array.from(document.querySelectorAll('.message-cell--main video')).map(v => {
const posterSrc = v.getAttribute('data-poster') || v.getAttribute('poster') || v.poster;
return {
type: 'video',
el: v,
src: v.currentSrc || v.querySelector('source')?.src,
poster: posterSrc,
width: v.videoWidth || 300,
height: v.videoHeight || 169,
id: v.src || Math.random().toString(36)
};
});
const posterUrls = new Set(videos.map(v => v.poster));
const images = Array.from(document.querySelectorAll('.message-cell--main img'))
.filter(img => {
const notPoster = !posterUrls.has(img.src);
const isLinkedFile = img.closest('.file.file--linked');
const isAttachment = img.src.includes('/attachments/') || img.src.includes('/data/attachments/');
if (img.width < 50 && img.height < 50) return false;
return (isLinkedFile || isAttachment) && notPoster;
})
.map(img => {
const parentLink = img.closest('a');
let fullUrl = img.src;
const w = parseInt(img.getAttribute('width') || img.naturalWidth || 0);
const h = parseInt(img.getAttribute('height') || img.naturalHeight || 0);
if (parentLink && parentLink.href) {
if (parentLink.href.includes('/attachments/')) {
fullUrl = parentLink.href;
}
else if (parentLink.href.match(/\.(jpg|jpeg|png|gif|webp)$/i)) {
fullUrl = parentLink.href;
}
}
const isGif = fullUrl.toLowerCase().endsWith('.gif');
return {
type: 'image',
subType: isGif ? 'gif' : 'img',
el: img,
src: fullUrl,
thumb: img.src,
width: w,
height: h,
id: fullUrl
};
});
return [...videos, ...images];
}
function buildGalleryUI() {
if (document.getElementById('pg-container')) return;
const container = document.createElement('div');
container.id = 'pg-container';
container.innerHTML = `
<div class="pg-header">
<div class="pg-filters">
<button class="pg-filter-btn active" data-filter="all">All (<span id="pg-count-all">0</span>)</button>
<button class="pg-filter-btn" data-filter="image">Images</button>
<button class="pg-filter-btn" data-filter="video">Videos</button>
</div>
<button class="pg-close">✖</button>
</div>
<div class="pg-gallery-scroll">
<div class="pg-grid" id="pg-grid"></div>
</div>
<div id="pg-lightbox">
<div class="pg-lb-header">
<span class="pg-lb-counter"></span>
</div>
<div class="pg-lb-content" id="pg-lb-stage">
<div class="pg-loader"></div>
</div>
<div class="pg-lb-controls">
<button class="pg-ctrl-btn" id="pg-prev">❮</button>
<button class="pg-ctrl-btn" id="pg-grid-view">${gridIconSvg}</button>
<button class="pg-ctrl-btn" id="pg-next">❯</button>
</div>
</div>
`;
document.body.appendChild(container);
galleryState.container = container;
galleryState.lightbox = container.querySelector('#pg-lightbox');
container.querySelectorAll('.pg-filter-btn').forEach(btn => {
btn.onclick = (e) => {
container.querySelectorAll('.pg-filter-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
galleryState.filter = e.target.dataset.filter;
renderGrid();
};
});
container.querySelector('.pg-close').onclick = closeGallery;
container.querySelector('#pg-grid-view').onclick = closeLightbox;
container.querySelector('#pg-prev').onclick = () => navLightbox(-1);
container.querySelector('#pg-next').onclick = () => navLightbox(1);
document.addEventListener('keydown', handleKeyInput);
container.addEventListener('wheel', (e) => {
const galleryScroll = container.querySelector('.pg-gallery-scroll');
const lb = container.querySelector('#pg-lightbox');
const isLbOpen = lb && lb.classList.contains('active');
if (isLbOpen) {
e.preventDefault();
return;
}
if (galleryScroll) {
e.preventDefault();
galleryScroll.scrollTop += e.deltaY;
}
}, { passive: false });
container.addEventListener('touchmove', (e) => {
const galleryScroll = container.querySelector('.pg-gallery-scroll');
const lb = container.querySelector('#pg-lightbox');
const isLbOpen = lb && lb.classList.contains('active');
if (isLbOpen) {
e.preventDefault();
return;
}
if (galleryScroll && galleryScroll.contains(e.target)) {
return;
}
e.preventDefault();
}, { passive: false });
let touchStartX = 0;
let touchEndX = 0;
const stage = document.getElementById('pg-lb-stage');
stage.addEventListener('touchstart', e => touchStartX = e.changedTouches[0].screenX);
stage.addEventListener('touchend', e => {
touchEndX = e.changedTouches[0].screenX;
if (touchEndX < touchStartX - 50) navLightbox(1);
if (touchEndX > touchStartX + 50) navLightbox(-1);
});
}
function reorderForColumns(items, numCols) {
const result = [];
for (let col = 0; col < numCols; col++) {
for (let i = col; i < items.length; i += numCols) {
result.push(items[i]);
}
}
return result;
}
function getColumnCount() {
const w = window.innerWidth;
if (w >= 1600) return 5;
if (w >= 1200) return 4;
if (w >= 768) return 3;
return 2;
}
function renderGrid() {
const grid = document.getElementById('pg-grid');
grid.innerHTML = '';
galleryState.filteredMedia = galleryState.media.filter(m =>
galleryState.filter === 'all' || m.type === galleryState.filter
);
document.getElementById('pg-count-all').innerText = galleryState.filteredMedia.length;
const numCols = getColumnCount();
const displayOrder = reorderForColumns(galleryState.filteredMedia, numCols);
displayOrder.forEach((item) => {
const index = galleryState.filteredMedia.indexOf(item);
const card = document.createElement('div');
card.className = 'pg-item';
let content = '';
let badge = '';
let imageNode = null;
if (item.type === 'video') {
const poster = item.poster ? item.poster : '';
content = poster ? `<img src="${poster}" loading="lazy">` : `<div style="height:150px; display:flex; align-items:center; justify-content:center; background:#222; color:#555;">No Thumb</div>`;
badge = '<span class="pg-badge video">VIDEO</span>';
} else {
imageNode = document.createElement('img');
imageNode.className = 'pg-lazy-img';
imageNode.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
imageNode.dataset.src = item.thumb;
imageNode.dataset.fullSrc = item.src;
imageNode.dataset.retries = 0;
if (item.width > 0 && item.height > 0) {
imageNode.style.aspectRatio = `${item.width} / ${item.height}`;
} else {
imageNode.style.minHeight = "200px";
}
imageNode.onload = function () {
this.classList.add('pg-loaded');
setStatusOverlay(card, 'clear');
};
imageNode.onerror = function () {
const fullSrc = this.dataset.fullSrc;
let retries = parseInt(this.dataset.retries || '0');
if (retries < config.maxRetries) {
retries++;
this.dataset.retries = retries;
setStatusOverlay(card, 'retry', `Retrying (${retries}/${config.maxRetries})`);
queueRetry(this, fullSrc, retries);
} else {
setStatusOverlay(card, 'broken', 'Image Broken');
this.classList.add('pg-loaded');
}
};
content = '';
badge = item.subType === 'gif' ? '<span class="pg-badge">GIF</span>' : '<span class="pg-badge">IMG</span>';
}
card.innerHTML = `${badge}${content}`;
if (imageNode) {
card.appendChild(imageNode);
}
card.onclick = () => openLightbox(index);
// Video hover preview — desktop only, skipped entirely on iOS
if (item.type === 'video' && !isIOS) {
let previewVideo = null;
let hoverTimeout = null;
card.onmouseenter = () => {
hoverTimeout = setTimeout(() => {
previewVideo = document.createElement('video');
previewVideo.className = 'pg-preview-video';
previewVideo.src = item.src;
previewVideo.muted = true;
previewVideo.loop = true;
previewVideo.playsInline = true;
card.appendChild(previewVideo);
previewVideo.play().catch(() => { });
}, 200);
};
card.onmouseleave = () => {
if (hoverTimeout) clearTimeout(hoverTimeout);
if (previewVideo) {
previewVideo.pause();
previewVideo.src = '';
previewVideo.remove();
previewVideo = null;
}
};
}
grid.appendChild(card);
});
setTimeout(initLazyObserver, 50);
}
function openGallery() {
galleryState.media = scanMedia();
if (galleryState.media.length === 0) {
alert("No media found on this page.");
return;
}
buildGalleryUI();
renderGrid();
lockBodyScroll();
requestAnimationFrame(() => {
document.getElementById('pg-container').classList.add('pg-visible');
});
}
function closeGallery() {
const c = document.getElementById('pg-container');
if (c) {
c.classList.remove('pg-visible');
setTimeout(() => c.remove(), 300);
unlockBodyScroll();
document.removeEventListener('keydown', handleKeyInput);
if (lazyImageObserver) lazyImageObserver.disconnect();
retryQueue.length = 0;
isRetrying = false;
}
}
function openLightbox(index) {
galleryState.currentIndex = index;
const lb = document.getElementById('pg-lightbox');
lb.classList.add('active');
updateLightboxContent();
}
function closeLightbox() {
const lb = document.getElementById('pg-lightbox');
const stage = document.getElementById('pg-lb-stage');
const existingVideo = stage.querySelector('video');
if (existingVideo) existingVideo.pause();
lb.classList.remove('active');
}
function navLightbox(direction) {
const newIndex = galleryState.currentIndex + direction;
if (newIndex >= 0 && newIndex < galleryState.filteredMedia.length) {
galleryState.currentIndex = newIndex;
updateLightboxContent();
}
}
function updateLightboxContent() {
const item = galleryState.filteredMedia[galleryState.currentIndex];
const stage = document.getElementById('pg-lb-stage');
const counter = document.querySelector('.pg-lb-counter');
const prevBtn = document.getElementById('pg-prev');
const nextBtn = document.getElementById('pg-next');
prevBtn.disabled = galleryState.currentIndex === 0;
nextBtn.disabled = galleryState.currentIndex === galleryState.filteredMedia.length - 1;
counter.innerText = `${galleryState.currentIndex + 1} / ${galleryState.filteredMedia.length}`;
stage.innerHTML = '<div class="pg-loader"></div>';
if (item.type === 'video') {
if (isIOS) {
// iOS: poster + play overlay → opens native AVPlayer
const wrapper = document.createElement('div');
wrapper.style.cssText = 'position:relative; display:flex; align-items:center; justify-content:center; max-width:100%; max-height:100%; cursor:pointer;';
if (item.poster) {
const img = document.createElement('img');
img.src = item.poster;
img.className = 'pg-lb-media';
img.onload = () => stage.querySelector('.pg-loader')?.remove();
img.onerror = () => stage.querySelector('.pg-loader')?.remove();
wrapper.appendChild(img);
} else {
stage.querySelector('.pg-loader')?.remove();
wrapper.style.width = '80%';
wrapper.style.height = '60%';
wrapper.style.background = '#111';
wrapper.style.borderRadius = '8px';
}
const playBtn = document.createElement('div');
playBtn.className = 'pg-ios-play-btn';
playBtn.innerHTML = `<svg viewBox="0 0 24 24" width="32" height="32" fill="white"><path d="M8 5v14l11-7z"/></svg>`;
playBtn.addEventListener('click', (e) => {
e.stopPropagation();
window.open(item.src, '_blank');
});
wrapper.addEventListener('click', (e) => {
window.open(item.src, '_blank');
});
wrapper.appendChild(playBtn);
stage.appendChild(wrapper);
} else {
const mediaEl = document.createElement('video');
mediaEl.className = 'pg-lb-media';
mediaEl.src = item.src;
mediaEl.controls = true;
mediaEl.playsInline = true;
mediaEl.autoplay = true;
mediaEl.volume = config.volume;
if (item.poster) mediaEl.poster = item.poster;
mediaEl.oncanplay = () => stage.querySelector('.pg-loader')?.remove();
mediaEl.onerror = () => stage.querySelector('.pg-loader')?.remove();
stage.appendChild(mediaEl);
}
} else {
const mediaEl = document.createElement('img');
mediaEl.className = 'pg-lb-media';
mediaEl.src = item.src;
mediaEl.onload = () => stage.querySelector('.pg-loader')?.remove();
mediaEl.onerror = () => {
stage.querySelector('.pg-loader')?.remove();
stage.insertAdjacentHTML('beforeend', '<div style="color:#fff; text-align:center;">Failed to load full size image.</div>');
};
stage.appendChild(mediaEl);
}
}
function handleKeyInput(e) {
const lb = document.getElementById('pg-lightbox');
const isLbOpen = lb && lb.classList.contains('active');
if (e.key === 'Escape') {
if (isLbOpen) closeLightbox();
else closeGallery();
} else if (isLbOpen) {
if (e.key === 'ArrowLeft') navLightbox(-1);
if (e.key === 'ArrowRight') navLightbox(1);
}
}
function createLaunchButton() {
const navGroups = document.querySelectorAll('.p-navgroup.p-account');
navGroups.forEach(navGroup => {
if (navGroup.querySelector('.pg-gallery-launcher')) return;
const btn = document.createElement("a");
btn.className = "p-navgroup-link u-ripple p-navgroup-link--iconic p-navgroup-link--iconic--media pg-gallery-launcher";
btn.title = "Open Gallery";
btn.href = "#";
btn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
openGallery();
};
btn.innerHTML = `
<i aria-hidden="true" class="fa fa-images"></i>
<span class="p-navgroup-linkText">Gallery</span>
`;
navGroup.appendChild(btn);
});
}
unlockVideos();
setTimeout(unlockVideos, 1500);
setTimeout(createLaunchButton, 500);
createLaunchButton();
})();