frontend now looks like firefox and chrome tabs on linux

This commit is contained in:
Joe Lothan 2026-05-25 22:53:11 -04:00
parent 7c4572aafb
commit 3ea88790b5
2 changed files with 123 additions and 67 deletions

View file

@ -57,6 +57,7 @@
image-rendering: auto; image-rendering: auto;
} }
/* ── Base tab styles ── */
.tab { .tab {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@ -68,68 +69,71 @@
min-width: 100px; min-width: 100px;
height: 36px; height: 36px;
transition: background 0.15s; transition: background 0.15s;
/* Default: Firefox inactive tab style */
border-radius: 4px;
border: none; border: none;
margin: 0 1px;
background: transparent; background: transparent;
} }
.tab:hover { /* ── Linux Firefox (default) ── */
background: var(--tab-hover); /* Inactive: transparent, small margin, rounded */
border-radius: 4px; .tab {
border-radius: 6px;
margin: 0 1px;
} }
/* Firefox selected tab — solid bg, rounded top, connects to content below */ /* Hover: pill-shaped background */
.browser-firefox .tab-active, .tab:hover {
.browser-firefox .tab-active: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); background: var(--tab-bg);
border-radius: 8px 8px 0 0; border-radius: 8px 8px 0 0;
box-shadow: 0 1px 0 0 var(--tab-bg); 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 { .browser-chrome .tab {
border-radius: 0; border-radius: 8px;
margin: 0; margin: 3px 2px;
padding: 6px 14px; padding: 3px 12px;
border-right: 1px solid var(--tab-border); 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 { .browser-chrome .tab:hover {
background: var(--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,
.browser-chrome .tab-active:hover { .browser-chrome .tab-active:hover {
background: var(--tab-bg); 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); 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 { .tab-about .tab-title {
font-weight: 600; font-weight: 600;
} }
@ -181,36 +185,69 @@
100% { transform: translateX(0); } 100% { transform: translateX(0); }
} }
/* Inline iframe viewer */ /* ── Inline iframe viewer ── */
.iframe-viewer { .iframe-viewer {
width: 100%; width: 100%;
height: 75vh; height: 75vh;
background: var(--viewer-bg); background: var(--viewer-bg);
border-top: 2px solid var(--tab-border);
border-bottom: 2px solid var(--tab-border); border-bottom: 2px solid var(--tab-border);
} }
.iframe-header { /* Toolbar area (address bar) */
.iframe-toolbar {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 6px 12px; padding: 8px 12px;
background: var(--tab-bg); background: var(--tab-bg);
} }
.iframe-header .tab-title { /* URL bar — rounded pill, looks like a browser address bar */
.iframe-urlbar {
flex: 1; 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 { /* Firefox: more rectangular address bar */
color: var(--text-muted); .browser-firefox .iframe-urlbar {
font-size: 12px; border-radius: 8px;
text-decoration: none;
} }
.iframe-header a:hover { .iframe-urlbar .tab-icon {
flex-shrink: 0;
}
.iframe-urlbar .url-text {
font-size: 14px;
line-height: 1;
color: var(--text); 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 { .iframe-close {
@ -219,8 +256,9 @@
color: var(--text); color: var(--text);
font-size: 16px; font-size: 16px;
cursor: pointer; cursor: pointer;
padding: 2px 6px; padding: 4px 8px;
border-radius: 4px; border-radius: 50%;
line-height: 1;
} }
.iframe-close:hover { .iframe-close:hover {
@ -229,7 +267,7 @@
.iframe-viewer iframe { .iframe-viewer iframe {
width: 100%; width: 100%;
height: calc(75vh - 36px); height: calc(75vh - 56px);
border: none; border: none;
background: white; background: white;
} }

View file

@ -14,16 +14,26 @@ const loadedBundles = new Set();
const container = document.getElementById("tab-container"); const container = document.getElementById("tab-container");
const loadingEl = document.getElementById("loading"); 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() { function detectBrowser() {
const ua = navigator.userAgent; const ua = navigator.userAgent;
if (ua.includes("Firefox")) return "firefox"; 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. 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 browserName = detectBrowser();
document.body.classList.add(`browser-${browserName}`); const osName = detectOS();
document.body.classList.add(`browser-${browserName}`, `os-${osName}`);
const isFirefox = browserName === "firefox"; const isFirefox = browserName === "firefox";
console.log(`EveryTab: browser=${browserName}, os=${osName}`);
// How many tabs fit in one row? // How many tabs fit in one row?
function tabsPerRow() { function tabsPerRow() {
@ -266,39 +276,47 @@ function openInlineViewer(tabEl, entry, url) {
const viewer = document.createElement("div"); const viewer = document.createElement("div");
viewer.className = "iframe-viewer"; viewer.className = "iframe-viewer";
const header = document.createElement("div"); // Toolbar (address bar area)
header.className = "iframe-header"; const toolbar = document.createElement("div");
toolbar.className = "iframe-toolbar";
// URL bar pill
const urlbar = document.createElement("div");
urlbar.className = "iframe-urlbar";
if (entry.icon) { if (entry.icon) {
const icon = document.createElement("img"); const icon = document.createElement("img");
icon.className = "tab-icon"; icon.className = "tab-icon";
icon.src = `data:image/png;base64,${entry.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"); const link = document.createElement("a");
link.className = "url-text";
link.href = url; link.href = url;
link.target = "_blank"; link.target = "_blank";
link.rel = "noopener"; link.rel = "noopener";
link.textContent = entry.url + " ↗"; link.textContent = entry.url;
header.appendChild(link); 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"); const close = document.createElement("button");
close.className = "iframe-close"; close.className = "iframe-close";
close.textContent = "✕"; close.textContent = "✕";
close.addEventListener("click", closeInlineViewer); close.addEventListener("click", closeInlineViewer);
header.appendChild(close); toolbar.appendChild(close);
const iframe = document.createElement("iframe"); const iframe = document.createElement("iframe");
iframe.sandbox = "allow-scripts allow-same-origin allow-forms"; iframe.sandbox = "allow-scripts allow-same-origin allow-forms";
iframe.src = url; iframe.src = url;
viewer.appendChild(header); viewer.appendChild(toolbar);
viewer.appendChild(iframe); viewer.appendChild(iframe);
// Insert after the row // Insert after the row