// Seeded PRNG (mulberry32) function mulberry32(seed) { return function() { seed |= 0; seed = seed + 0x6D2B79F5 | 0; let t = Math.imul(seed ^ seed >>> 15, 1 | seed); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; } const rng = mulberry32(Date.now()); const loadedBundles = new Set(); const container = document.getElementById("tab-container"); const loadingEl = document.getElementById("loading"); const showAll = new URLSearchParams(window.location.search).has("all"); // Detect browser and OS, set classes on body for tab styling function detectBrowser() { const ua = navigator.userAgent; // Mobile defaults to chrome styling if (/iPhone|iPad|Android/.test(ua)) return "chrome"; if (ua.includes("Firefox")) return "firefox"; if (ua.includes("Edg/")) return "edge"; if (ua.includes("Safari") && !ua.includes("Chrome") && !ua.includes("CriOS")) return "safari"; return "chrome"; // Chrome, Edge, Opera, Brave, etc. } function detectOS() { const ua = navigator.userAgent; if (ua.includes("Windows")) return "windows"; if (ua.includes("Macintosh") || ua.includes("Mac OS")) return "mac"; if (ua.includes("iPhone") || ua.includes("iPad")) return "mac"; return "linux"; // Linux, Android, ChromeOS, unknown } const browserName = detectBrowser(); const osName = detectOS(); document.body.classList.add(`browser-${browserName}`, `os-${osName}`); const isFirefox = browserName === "firefox"; console.log(`EveryTab: browser=${browserName}, os=${osName}`); // How many tabs fit in one row? function tabsPerRow() { const tabWidth = 160; // avg tab width in px (min 100, max 200) return Math.ceil(window.innerWidth / tabWidth) + 4; // extra for marquee overflow } // How many rows fill the viewport? function rowsPerScreen() { const rowHeight = 40; return Math.ceil(window.innerHeight / rowHeight) + 4; // buffer } // Pick a random bundle index we haven't loaded yet function pickBundle() { if (loadedBundles.size >= TOTAL_BUNDLES) return -1; let idx; do { idx = Math.floor(rng() * TOTAL_BUNDLES); } while (loadedBundles.has(idx)); loadedBundles.add(idx); return idx; } // Fetch a bundle JSON async function fetchBundle(idx) { const padded = String(idx).padStart(6, "0"); const resp = await fetch(`tabs/${padded}.json`); if (!resp.ok) throw new Error(`Failed to fetch bundle ${padded}`); const data = await resp.json(); return data.entries; } // Create a tab DOM element function createTab(entry) { const tab = document.createElement("div"); tab.className = "tab"; if (!entry.iframe_ok) { tab.classList.add("tab-external"); } if (entry._isAbout) { // About tab gets a rotating icon like the site favicon const img = document.createElement("img"); img.className = "tab-icon"; img.alt = ""; aboutIcons.push(img); tab.appendChild(img); tab.classList.add("tab-about"); } else if (entry.icon) { const img = document.createElement("img"); img.className = "tab-icon"; img.src = `data:image/png;base64,${entry.icon}`; img.alt = ""; img.loading = "lazy"; tab.appendChild(img); if (loadedIcons.length < 100) loadedIcons.push(entry.icon); } const title = document.createElement("span"); title.className = "tab-title"; title.textContent = entry.title || entry.url; tab.appendChild(title); tab.title = entry.title || entry.url; // Click handler tab.addEventListener("click", () => { if (entry._isAbout) { openInlineViewer(tab, entry, "/about.html"); return; } if (entry.iframe_ok) { openInlineViewer(tab, entry, entry.url); } else { window.open(entry.url, "_blank", "noopener"); } }); return tab; } // Create a row of tabs with marquee animation function createRow(entries, rowIndex) { const row = document.createElement("div"); row.className = "tab-row"; const pxPerSec = 15 + (rng() * 10); // 15-25 px/sec, consistent across screen sizes const goLeft = rowIndex === 0 ? true : rng() > 0.5; const stagger = rowIndex === 0 ? 0 : rng(); // Store config — animation starts after DOM insertion (needs measured width) row._animConfig = { pxPerSec, goLeft, stagger }; // CSS animation classes for Chrome/Safari (Firefox uses rAF in startRowAnimation) if (!isFirefox) { row.classList.add(goLeft ? "scroll-left" : "scroll-right"); } // Add tabs twice so the marquee loops seamlessly (translate -50% = one full set) for (let copy = 0; copy < 2; copy++) { for (const entry of entries) { row.appendChild(createTab(entry)); } } return row; } // Start animation (must be called after DOM insertion) function startRowAnimation(row) { const { pxPerSec, goLeft, stagger } = row._animConfig; const halfWidth = row.scrollWidth / 2; if (isFirefox) { // Firefox: rAF with continuous sub-pixel positions — no loop boundary, // no compositor iteration restart flicker. filter: blur(0px) in CSS // prevents Firefox's device-pixel snapping. let pos = stagger * halfWidth; let lastTime = null; row._rafId = true; function tick(now) { if (row._paused) { lastTime = null; requestAnimationFrame(tick); return; } if (lastTime === null) { lastTime = now; requestAnimationFrame(tick); return; } pos += ((now - lastTime) / 1000) * pxPerSec; lastTime = now; if (pos >= halfWidth) pos -= halfWidth; row.style.transform = `translateX(${goLeft ? -pos : -(halfWidth - pos)}px)`; requestAnimationFrame(tick); } requestAnimationFrame(tick); } else { // Chrome/Safari: CSS @keyframes animation with calculated duration const duration = halfWidth / pxPerSec; row.style.setProperty("--speed", `${duration}s`); row.style.animationDelay = `${-stagger * duration}s`; } } // Render entries into rows function renderEntries(entries) { const perRow = tabsPerRow(); let rowIndex = container.children.length; for (let i = 0; i < entries.length; i += perRow) { const rowEntries = entries.slice(i, i + perRow); if (rowEntries.length < 3) continue; // skip tiny last rows const row = createRow(rowEntries, rowIndex); container.appendChild(row); startRowAnimation(row); rowIndex++; } } // Load enough bundles to fill the screen async function loadMore() { const neededRows = rowsPerScreen(); const entriesNeeded = neededRows * tabsPerRow(); let entries = []; while (entries.length < entriesNeeded) { const idx = pickBundle(); if (idx === -1) break; try { let bundleEntries = await fetchBundle(idx); if (!showAll) bundleEntries = bundleEntries.filter(e => e.iframe_ok); entries = entries.concat(bundleEntries); } catch (e) { console.error("Failed to load bundle:", e); } } if (entries.length > 0) { // Inject the "About EveryTab" tab in the first row on first load const isFirstLoad = container.children.length === 0; if (isFirstLoad) { const aboutEntry = { url: "https://everytab.site/about.html", title: "About EveryTab", icon: "", iframe_ok: true, _isAbout: true, }; entries.splice(0, 0, aboutEntry); } renderEntries(entries); loadingEl.style.display = "none"; // Auto-open the about tab on first load if (isFirstLoad) { const aboutTab = container.querySelector(".tab-about"); if (aboutTab) { aboutTab.click(); } } } } // Rotating site favicon — picks a random loaded icon every second const faviconLink = document.createElement("link"); faviconLink.rel = "icon"; faviconLink.type = "image/png"; document.head.appendChild(faviconLink); const loadedIcons = []; const aboutIcons = []; // img elements for "About" tabs — rotate their icons too setInterval(() => { if (loadedIcons.length === 0) return; const icon = loadedIcons[Math.floor(Math.random() * loadedIcons.length)]; const dataUri = `data:image/png;base64,${icon}`; faviconLink.href = dataUri; for (const img of aboutIcons) { img.src = dataUri; } }, 1000); // Infinite scroll let loading = false; window.addEventListener("scroll", async () => { if (loading) return; const scrollBottom = window.innerHeight + window.scrollY; const docHeight = document.documentElement.scrollHeight; if (scrollBottom >= docHeight - 500) { loading = true; await loadMore(); loading = false; } }); // Inline iframe viewer let activeViewer = null; let activeTab = null; let activeRow = null; function openInlineViewer(tabEl, entry, url) { // Close existing viewer closeInlineViewer(); // Mark tab as active tabEl.classList.add("tab-active"); activeTab = tabEl; // Find the row this tab belongs to const row = tabEl.closest(".tab-row"); // Pause the marquee on the selected tab's row row.classList.add("paused"); if (row._rafId) { // Firefox rAF: store paused state row._paused = true; } else { // CSS animation: getAnimations() to pause row.getAnimations().forEach(a => a.pause()); } // Build the viewer const viewer = document.createElement("div"); viewer.className = "iframe-viewer"; const isAbout = entry._isAbout; // Title bar (hidden on desktop, shown on mobile) const titlebar = document.createElement("div"); titlebar.className = "iframe-titlebar"; titlebar.textContent = entry.title || entry.url; viewer.appendChild(titlebar); // Toolbar (address bar area) const toolbar = document.createElement("div"); toolbar.className = "iframe-toolbar"; const urlbar = document.createElement("div"); urlbar.className = "iframe-urlbar"; if (entry.icon) { const icon = document.createElement("img"); icon.className = "tab-icon"; icon.src = `data:image/png;base64,${entry.icon}`; urlbar.appendChild(icon); } const link = document.createElement("a"); link.className = "url-text"; link.href = url; link.target = "_blank"; link.rel = "noopener"; link.textContent = entry.url; urlbar.appendChild(link); const urlTitle = document.createElement("span"); urlTitle.className = "url-title"; urlTitle.textContent = entry.title || ""; urlbar.appendChild(urlTitle); toolbar.appendChild(urlbar); const close = document.createElement("button"); close.className = "iframe-close"; close.textContent = "✕"; close.addEventListener("click", closeInlineViewer); toolbar.appendChild(close); viewer.appendChild(toolbar); const iframe = document.createElement("iframe"); iframe.sandbox = "allow-scripts allow-same-origin allow-forms"; iframe.src = url; // About page: auto-size iframe to content height (same-origin) if (isAbout) { iframe.addEventListener("load", () => { try { const body = iframe.contentDocument.body; const lastEl = body.lastElementChild; const contentHeight = lastEl.offsetTop + lastEl.offsetHeight + parseInt(getComputedStyle(body).marginTop) + parseInt(getComputedStyle(body).marginBottom); const chromeHeight = viewer.offsetHeight - iframe.clientHeight; viewer.style.height = (contentHeight + chromeHeight) + "px"; } catch (e) {} }); } viewer.appendChild(iframe); // Insert after the row row.after(viewer); activeViewer = viewer; activeRow = row; // Scroll so the tab row + viewer are visible row.scrollIntoView({ behavior: "smooth", block: "start" }); } function closeInlineViewer() { if (activeViewer) { activeViewer.remove(); activeViewer = null; } if (activeRow) { activeRow.classList.remove("paused"); if (activeRow._paused) { activeRow._paused = false; } else { activeRow.getAnimations().forEach(a => a.play()); } activeRow = null; } if (activeTab) { activeTab.classList.remove("tab-active"); activeTab = null; } } document.addEventListener("keydown", (e) => { if (e.key === "Escape") closeInlineViewer(); }); // Initial load loadMore();