| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| <meta name="color-scheme" content="dark" /> |
| <meta name="theme-color" content="#05121a" /> |
| <link rel="preconnect" href="https://fonts.googleapis.com" /> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> |
| <link |
| href="https://fonts.googleapis.com/css2?family=Martian+Mono:wght@400;500;600;700;800&family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,600;1,400;1,500&display=swap" |
| rel="stylesheet" |
| /> |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| <script> |
| (function () { |
| var THRESHOLD = 48; |
| var PANELS = ["feed-html", "stage-html"]; |
| |
| function scroller(panel) { |
| return panel.querySelector(".feed.scroll") || panel.querySelector(".feed"); |
| } |
| |
| function ensureButton(panel) { |
| panel.style.position = "relative"; |
| var btn = panel.querySelector(":scope > .feed-jump"); |
| if (!btn) { |
| btn = document.createElement("button"); |
| btn.type = "button"; |
| btn.className = "feed-jump"; |
| btn.setAttribute("aria-label", "Jump to the latest message"); |
| btn.innerHTML = |
| '<span class="fj-pulse"></span>' + |
| '<span class="fj-label">live</span>' + |
| '<span class="fj-arrow" aria-hidden="true">↓</span>'; |
| btn.addEventListener("click", function () { |
| panel.__pinned = true; |
| var sc = scroller(panel); |
| if (sc) sc.scrollTop = sc.scrollHeight; |
| refresh(panel); |
| }); |
| panel.appendChild(btn); |
| } |
| return btn; |
| } |
| |
| function refresh(panel) { |
| var sc = scroller(panel); |
| var btn = ensureButton(panel); |
| if (!sc) { |
| btn.classList.remove("show"); |
| return; |
| } |
| if (panel.__pinned == null) panel.__pinned = true; |
| if (panel.__pinned) { |
| sc.scrollTop = sc.scrollHeight; |
| } else if (panel.__lastTop != null) { |
| |
| |
| sc.scrollTop = Math.min(panel.__lastTop, sc.scrollHeight); |
| } |
| var dist = sc.scrollHeight - sc.scrollTop - sc.clientHeight; |
| btn.classList.toggle("show", !panel.__pinned && dist > THRESHOLD); |
| } |
| |
| function bindScroll(panel) { |
| var sc = scroller(panel); |
| if (!sc || sc.__fjBound) return; |
| sc.__fjBound = true; |
| sc.addEventListener( |
| "scroll", |
| function () { |
| panel.__lastTop = sc.scrollTop; |
| var dist = sc.scrollHeight - sc.scrollTop - sc.clientHeight; |
| panel.__pinned = dist <= THRESHOLD; |
| ensureButton(panel).classList.toggle("show", !panel.__pinned && dist > THRESHOLD); |
| }, |
| { passive: true } |
| ); |
| } |
| |
| function manage(panel) { |
| if (panel.__fjManaged) return; |
| panel.__fjManaged = true; |
| panel.__pinned = true; |
| ensureButton(panel); |
| bindScroll(panel); |
| refresh(panel); |
| new MutationObserver(function () { |
| bindScroll(panel); |
| refresh(panel); |
| }).observe(panel, { childList: true, subtree: true }); |
| } |
| |
| function sweep() { |
| for (var i = 0; i < PANELS.length; i++) { |
| var panel = document.getElementById(PANELS[i]); |
| if (panel) manage(panel); |
| } |
| } |
| |
| |
| |
| setInterval(sweep, 800); |
| if (document.readyState !== "loading") sweep(); |
| else document.addEventListener("DOMContentLoaded", sweep); |
| })(); |
| </script> |
|
|