From 3ea88790b52f5419bb9c3e0b3519b4cf35c5d236 Mon Sep 17 00:00:00 2001 From: Joe Lothan Date: Mon, 25 May 2026 22:53:11 -0400 Subject: [PATCH] frontend now looks like firefox and chrome tabs on linux --- frontend/index.html | 142 ++++++++++++++++++++++++++++---------------- frontend/site.js | 48 ++++++++++----- 2 files changed, 123 insertions(+), 67 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 9cfe985..a71200a 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -57,6 +57,7 @@ image-rendering: auto; } + /* ── Base tab styles ── */ .tab { display: inline-flex; align-items: center; @@ -68,68 +69,71 @@ min-width: 100px; height: 36px; transition: background 0.15s; - /* Default: Firefox inactive tab style */ - border-radius: 4px; border: none; - margin: 0 1px; background: transparent; } - .tab:hover { - background: var(--tab-hover); - border-radius: 4px; + /* ── Linux Firefox (default) ── */ + /* Inactive: transparent, small margin, rounded */ + .tab { + border-radius: 6px; + margin: 0 1px; } - /* Firefox selected tab — solid bg, rounded top, connects to content below */ - .browser-firefox .tab-active, - .browser-firefox .tab-active:hover { + /* Hover: pill-shaped background */ + .tab:hover { + background: var(--tab-hover); + border-radius: 8px; + } + + /* Selected: white bg, rounded top, connects to viewer below */ + .tab-active, + .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 */ + /* ── Linux Chrome ── */ + /* Inactive: always inset dimensions, separator via ::before */ .browser-chrome .tab { - border-radius: 0; - margin: 0; - padding: 6px 14px; - border-right: 1px solid var(--tab-border); + border-radius: 8px; + margin: 3px 2px; + padding: 3px 12px; + height: 30px; + position: relative; } + .browser-chrome .tab::before { + content: ""; + position: absolute; + left: -2px; + top: 25%; + height: 50%; + width: 1px; + background: var(--tab-border); + } + + /* Hide separator on hover/active tab AND on the tab after hover/active */ + .browser-chrome .tab:hover::before, + .browser-chrome .tab-active::before, + .browser-chrome .tab:hover + .tab::before, + .browser-chrome .tab-active + .tab::before { + background: transparent; + } + + /* Hover: just add background (no layout change) */ .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 */ + /* Selected: white bg, subtle shadow */ .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-about .tab-title { font-weight: 600; } @@ -181,36 +185,69 @@ 100% { transform: translateX(0); } } - /* Inline iframe viewer */ + /* ── Inline iframe viewer ── */ .iframe-viewer { width: 100%; height: 75vh; background: var(--viewer-bg); - border-top: 2px solid var(--tab-border); border-bottom: 2px solid var(--tab-border); } - .iframe-header { + /* Toolbar area (address bar) */ + .iframe-toolbar { display: flex; align-items: center; gap: 8px; - padding: 6px 12px; + padding: 8px 12px; background: var(--tab-bg); } - .iframe-header .tab-title { + /* URL bar — rounded pill, looks like a browser address bar */ + .iframe-urlbar { flex: 1; - font-size: 13px; + display: flex; + align-items: center; + gap: 8px; + padding: 10px 16px; + background: var(--bg); + border-radius: 24px; + min-width: 0; } - .iframe-header a { - color: var(--text-muted); - font-size: 12px; - text-decoration: none; + /* Firefox: more rectangular address bar */ + .browser-firefox .iframe-urlbar { + border-radius: 8px; } - .iframe-header a:hover { + .iframe-urlbar .tab-icon { + flex-shrink: 0; + } + + .iframe-urlbar .url-text { + font-size: 14px; + line-height: 1; color: var(--text); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-decoration: none; + flex-shrink: 0; + } + + .iframe-urlbar .url-text:hover { + text-decoration: underline; + } + + .iframe-urlbar .url-title { + font-size: 12px; + line-height: 1; + color: var(--text-muted); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: right; + flex: 1; + min-width: 0; } .iframe-close { @@ -219,8 +256,9 @@ color: var(--text); font-size: 16px; cursor: pointer; - padding: 2px 6px; - border-radius: 4px; + padding: 4px 8px; + border-radius: 50%; + line-height: 1; } .iframe-close:hover { @@ -229,7 +267,7 @@ .iframe-viewer iframe { width: 100%; - height: calc(75vh - 36px); + height: calc(75vh - 56px); border: none; background: white; } diff --git a/frontend/site.js b/frontend/site.js index 03d785d..b1dab7f 100644 --- a/frontend/site.js +++ b/frontend/site.js @@ -14,16 +14,26 @@ 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 +// Detect browser and OS, set classes 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"; + 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(); -document.body.classList.add(`browser-${browserName}`); +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() { @@ -266,39 +276,47 @@ function openInlineViewer(tabEl, entry, url) { const viewer = document.createElement("div"); viewer.className = "iframe-viewer"; - const header = document.createElement("div"); - header.className = "iframe-header"; + // Toolbar (address bar area) + const toolbar = document.createElement("div"); + toolbar.className = "iframe-toolbar"; + + // URL bar pill + 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}`; - header.appendChild(icon); + urlbar.appendChild(icon); } - const title = document.createElement("span"); - title.className = "tab-title"; - title.textContent = entry.title || entry.url; - header.appendChild(title); - const link = document.createElement("a"); + link.className = "url-text"; link.href = url; link.target = "_blank"; link.rel = "noopener"; - link.textContent = entry.url + " ↗"; - header.appendChild(link); + 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); - header.appendChild(close); + toolbar.appendChild(close); const iframe = document.createElement("iframe"); iframe.sandbox = "allow-scripts allow-same-origin allow-forms"; iframe.src = url; - viewer.appendChild(header); + viewer.appendChild(toolbar); viewer.appendChild(iframe); // Insert after the row