// 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"); // 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(4, "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.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); } const title = document.createElement("span"); title.className = "tab-title"; title.textContent = entry.title || entry.host; tab.appendChild(title); tab.title = entry.title || entry.host; // Click handler tab.addEventListener("click", () => { const url = `${entry.protocol || "https"}://${entry.host}`; if (entry.iframe_ok) { openInlineViewer(tab, entry, url); } else { window.open(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 speed = 60 + (rng() * 40); // 60-100s per cycle row.style.setProperty("--speed", `${speed}s`); // Random direction const goLeft = rng() > 0.5; row.classList.add(goLeft ? "scroll-left" : "scroll-right"); // Stagger start so rows aren't synchronized row.style.animationDelay = `${-rng() * speed}s`; // 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; } // 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 container.appendChild(createRow(rowEntries, rowIndex)); 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 { const bundleEntries = await fetchBundle(idx); entries = entries.concat(bundleEntries); } catch (e) { console.error("Failed to load bundle:", e); } } if (entries.length > 0) { renderEntries(entries); loadingEl.style.display = "none"; } } // 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; function openInlineViewer(tabEl, entry, url) { // Close existing viewer closeInlineViewer(); // Find the row this tab belongs to const row = tabEl.closest(".tab-row"); // Build the viewer const viewer = document.createElement("div"); viewer.className = "iframe-viewer"; const header = document.createElement("div"); header.className = "iframe-header"; if (entry.icon) { const icon = document.createElement("img"); icon.className = "tab-icon"; icon.src = `data:image/png;base64,${entry.icon}`; header.appendChild(icon); } const title = document.createElement("span"); title.className = "tab-title"; title.textContent = entry.title || entry.host; header.appendChild(title); const link = document.createElement("a"); link.href = url; link.target = "_blank"; link.rel = "noopener"; link.textContent = entry.host + " ↗"; header.appendChild(link); const close = document.createElement("button"); close.className = "iframe-close"; close.textContent = "✕"; close.addEventListener("click", closeInlineViewer); header.appendChild(close); const iframe = document.createElement("iframe"); iframe.sandbox = "allow-scripts allow-same-origin allow-forms"; iframe.src = url; viewer.appendChild(header); viewer.appendChild(iframe); // Insert after the row row.after(viewer); activeViewer = viewer; // Scroll so the viewer is visible viewer.scrollIntoView({ behavior: "smooth", block: "start" }); } function closeInlineViewer() { if (activeViewer) { activeViewer.remove(); activeViewer = null; } } document.addEventListener("keydown", (e) => { if (e.key === "Escape") closeInlineViewer(); }); // Initial load loadMore();