From 4492d225db2de3f90c2ae766b65de4c0e9e51d6a Mon Sep 17 00:00:00 2001 From: Joe Lothan Date: Sun, 17 May 2026 23:50:56 -0400 Subject: [PATCH] fancier frontend --- frontend/index.html | 109 ++++++++++++++++++++++++++++++++++++-------- frontend/site.js | 20 +++++++- 2 files changed, 110 insertions(+), 19 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index b49d604..9dce13f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -11,8 +11,30 @@ box-sizing: border-box; } + :root { + --bg: #f0f0f4; + --tab-bg: #ffffff; + --tab-hover: #e8e8ed; + --tab-border: #cfcfd8; + --text: #15141a; + --text-muted: #5b5b66; + --viewer-bg: #f0f0f4; + } + + @media (prefers-color-scheme: dark) { + :root { + --bg: #1c1b22; + --tab-bg: #2b2a33; + --tab-hover: #42414d; + --tab-border: #3a3944; + --text: #fbfbfe; + --text-muted: #8f8f9d; + --viewer-bg: #1c1b22; + } + } + body { - background: #1c1b22; + background: var(--bg); overflow-x: hidden; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } @@ -29,21 +51,72 @@ align-items: center; gap: 6px; padding: 6px 12px; - margin: 0 1px; - background: #2b2a33; - border-radius: 8px 8px 0 0; cursor: pointer; flex-shrink: 0; max-width: 200px; min-width: 100px; height: 36px; - border: 1px solid #3a3944; - border-bottom: none; transition: background 0.15s; + /* Default: Firefox inactive tab style */ + border-radius: 4px; + border: none; + margin: 0 1px; + background: transparent; } .tab:hover { - background: #42414d; + background: var(--tab-hover); + border-radius: 4px; + } + + /* Firefox selected tab — solid bg, rounded top, connects to content below */ + .browser-firefox .tab-active, + .browser-firefox .tab-active:hover { + background: var(--tab-bg); + border-radius: 8px 8px 0 0; + box-shadow: 0 1px 0 0 var(--tab-bg); + } + + /* Chrome / Chromium — inactive tabs: no bg, thin separators between */ + .browser-chrome .tab { + border-radius: 0; + margin: 0; + padding: 6px 14px; + border-right: 1px solid var(--tab-border); + } + + .browser-chrome .tab:hover { + background: var(--tab-hover); + border-radius: 8px 8px 0 0; + border-right: 1px solid transparent; + } + + /* Chrome selected tab — raised, rounded top, solid bg */ + .browser-chrome .tab-active, + .browser-chrome .tab-active:hover { + background: var(--tab-bg); + border-radius: 10px 10px 0 0; + border-right: 1px solid transparent; + box-shadow: 0 -1px 4px rgba(0, 0, 0, 0.1); + } + + /* Safari — flat pill, very minimal */ + .browser-safari .tab { + border-radius: 6px; + margin: 0 2px; + height: 32px; + padding: 4px 10px; + } + + .browser-safari .tab:hover { + background: var(--tab-hover); + } + + /* Safari selected tab — solid pill, slightly elevated */ + .browser-safari .tab-active, + .browser-safari .tab-active:hover { + background: var(--tab-bg); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); } .tab-icon { @@ -55,7 +128,7 @@ .tab-title { font-size: 12px; - color: #fbfbfe; + color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -68,7 +141,7 @@ .tab-external::after { content: "↗"; font-size: 9px; - color: #8f8f9d; + color: var(--text-muted); position: absolute; top: 3px; right: 4px; @@ -97,9 +170,9 @@ .iframe-viewer { width: 100%; height: 75vh; - background: #1c1b22; - border-top: 2px solid #3a3944; - border-bottom: 2px solid #3a3944; + background: var(--viewer-bg); + border-top: 2px solid var(--tab-border); + border-bottom: 2px solid var(--tab-border); } .iframe-header { @@ -107,7 +180,7 @@ align-items: center; gap: 8px; padding: 6px 12px; - background: #2b2a33; + background: var(--tab-bg); } .iframe-header .tab-title { @@ -116,19 +189,19 @@ } .iframe-header a { - color: #8f8f9d; + color: var(--text-muted); font-size: 12px; text-decoration: none; } .iframe-header a:hover { - color: #fbfbfe; + color: var(--text); } .iframe-close { background: none; border: none; - color: #fbfbfe; + color: var(--text); font-size: 16px; cursor: pointer; padding: 2px 6px; @@ -136,7 +209,7 @@ } .iframe-close:hover { - background: #42414d; + background: var(--tab-hover); } .iframe-viewer iframe { @@ -147,7 +220,7 @@ } #loading { - color: #8f8f9d; + color: var(--text-muted); text-align: center; padding: 20px; font-size: 14px; diff --git a/frontend/site.js b/frontend/site.js index f21ad53..04909cd 100644 --- a/frontend/site.js +++ b/frontend/site.js @@ -14,6 +14,15 @@ const loadedBundles = new Set(); const container = document.getElementById("tab-container"); const loadingEl = document.getElementById("loading"); +// Detect browser and set class on body for tab styling +function detectBrowser() { + const ua = navigator.userAgent; + if (ua.includes("Firefox")) return "firefox"; + if (ua.includes("Safari") && !ua.includes("Chrome")) return "safari"; + return "chrome"; // Chrome, Edge, Opera, Brave, etc. +} +document.body.classList.add(`browser-${detectBrowser()}`); + // How many tabs fit in one row? function tabsPerRow() { const tabWidth = 160; // avg tab width in px (min 100, max 200) @@ -88,7 +97,7 @@ function createRow(entries, rowIndex) { const row = document.createElement("div"); row.className = "tab-row"; - const speed = 60 + (rng() * 40); // 60-100s per cycle + const speed = 90 + (rng() * 60); // 90-150s per cycle row.style.setProperty("--speed", `${speed}s`); // Random direction @@ -160,11 +169,16 @@ window.addEventListener("scroll", async () => { // Inline iframe viewer let activeViewer = null; +let activeTab = 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"); @@ -220,6 +234,10 @@ function closeInlineViewer() { activeViewer.remove(); activeViewer = null; } + if (activeTab) { + activeTab.classList.remove("tab-active"); + activeTab = null; + } } document.addEventListener("keydown", (e) => {