Performance fixes for UI
This commit is contained in:
+79
-89
@@ -1,4 +1,7 @@
|
|||||||
const ipcRenderer = window.electronAPI;
|
const ipcRenderer = window.electronAPI;
|
||||||
|
// Lightweight debug logger (toggleable)
|
||||||
|
const DEBUG = false;
|
||||||
|
const debug = (...args) => { if (DEBUG) console.log(...args); };
|
||||||
|
|
||||||
// Site history management using localStorage
|
// Site history management using localStorage
|
||||||
function getSiteHistory() {
|
function getSiteHistory() {
|
||||||
@@ -35,6 +38,7 @@ const webviewsEl = document.getElementById('webviews');
|
|||||||
const menuPopup = document.getElementById('menu-popup');
|
const menuPopup = document.getElementById('menu-popup');
|
||||||
const contextMenu = document.getElementById('context-menu');
|
const contextMenu = document.getElementById('context-menu');
|
||||||
const menuItems = contextMenu ? contextMenu.querySelectorAll('li') : [];
|
const menuItems = contextMenu ? contextMenu.querySelectorAll('li') : [];
|
||||||
|
let lastContextPos = { x: 0, y: 0 };
|
||||||
|
|
||||||
// Select all text on focus and prevent mouseup from deselecting
|
// Select all text on focus and prevent mouseup from deselecting
|
||||||
urlBox.addEventListener('focus', () => {
|
urlBox.addEventListener('focus', () => {
|
||||||
@@ -64,6 +68,19 @@ function scheduleRenderTabs() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debounce nav button updates to reduce layout work
|
||||||
|
let navButtonsPending = false;
|
||||||
|
let backBtnCached = null;
|
||||||
|
let fwdBtnCached = null;
|
||||||
|
function scheduleUpdateNavButtons() {
|
||||||
|
if (navButtonsPending) return;
|
||||||
|
navButtonsPending = true;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
navButtonsPending = false;
|
||||||
|
try { updateNavButtons(); } catch {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Derive a stable, safe label for a tab without throwing on non-URLs
|
// Derive a stable, safe label for a tab without throwing on non-URLs
|
||||||
function getTabLabel(tab) {
|
function getTabLabel(tab) {
|
||||||
if (tab.title && tab.title !== 'New Tab') return tab.title;
|
if (tab.title && tab.title !== 'New Tab') return tab.title;
|
||||||
@@ -105,13 +122,13 @@ loadBookmarks();
|
|||||||
|
|
||||||
// Listen for site history updates from main process
|
// Listen for site history updates from main process
|
||||||
ipcRenderer.on('record-site-history', (event, url) => {
|
ipcRenderer.on('record-site-history', (event, url) => {
|
||||||
console.log('[DEBUG] Received site history update:', url);
|
debug('[DEBUG] Received site history update:', url);
|
||||||
addToSiteHistory(url);
|
addToSiteHistory(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
function createTab(inputUrl) {
|
function createTab(inputUrl) {
|
||||||
inputUrl = inputUrl || 'browser://home';
|
inputUrl = inputUrl || 'browser://home';
|
||||||
console.log('[DEBUG] createTab() inputUrl =', inputUrl);
|
debug('[DEBUG] createTab() inputUrl =', inputUrl);
|
||||||
const id = crypto.randomUUID();
|
const id = crypto.randomUUID();
|
||||||
|
|
||||||
// Handle home page specially
|
// Handle home page specially
|
||||||
@@ -139,7 +156,7 @@ function createTab(inputUrl) {
|
|||||||
|
|
||||||
// For all other URLs, use webview
|
// For all other URLs, use webview
|
||||||
const resolvedUrl = resolveInternalUrl(inputUrl);
|
const resolvedUrl = resolveInternalUrl(inputUrl);
|
||||||
console.log('[DEBUG] createTab() resolvedUrl =', resolvedUrl);
|
debug('[DEBUG] createTab() resolvedUrl =', resolvedUrl);
|
||||||
|
|
||||||
const webview = document.createElement('webview');
|
const webview = document.createElement('webview');
|
||||||
// give the webview an id and set its source and attributes so it actually loads and can be managed
|
// give the webview an id and set its source and attributes so it actually loads and can be managed
|
||||||
@@ -173,37 +190,17 @@ function createTab(inputUrl) {
|
|||||||
// Consolidated navigation recording - only use did-navigate to avoid duplicates
|
// Consolidated navigation recording - only use did-navigate to avoid duplicates
|
||||||
webview.addEventListener('did-navigate', e => {
|
webview.addEventListener('did-navigate', e => {
|
||||||
handleNavigation(id, e.url);
|
handleNavigation(id, e.url);
|
||||||
// Record ALL HTTP navigations
|
if (e.url.startsWith('http')) debug('[DEBUG] Recording navigation to:', e.url);
|
||||||
if (e.url.startsWith('http')) {
|
|
||||||
console.log('[DEBUG] Recording navigation to:', e.url);
|
|
||||||
addToSiteHistory(e.url);
|
|
||||||
// Also save to file for cross-context sharing
|
|
||||||
ipcRenderer.invoke('save-site-history-entry', e.url).catch(err =>
|
|
||||||
console.error('Failed to save to file:', err)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
webview.addEventListener('did-navigate-in-page', e => {
|
webview.addEventListener('did-navigate-in-page', e => {
|
||||||
handleNavigation(id, e.url);
|
handleNavigation(id, e.url);
|
||||||
// Record in-page navigations too
|
if (e.url.startsWith('http')) debug('[DEBUG] Recording in-page navigation to:', e.url);
|
||||||
if (e.url.startsWith('http')) {
|
|
||||||
console.log('[DEBUG] Recording in-page navigation to:', e.url);
|
|
||||||
addToSiteHistory(e.url);
|
|
||||||
ipcRenderer.invoke('save-site-history-entry', e.url).catch(err =>
|
|
||||||
console.error('Failed to save to file:', err)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Also capture when pages finish loading
|
// After load, just refresh nav buttons to avoid jank
|
||||||
webview.addEventListener('did-finish-load', () => {
|
webview.addEventListener('did-finish-load', () => {
|
||||||
const currentUrl = webview.getURL();
|
scheduleUpdateNavButtons();
|
||||||
if (currentUrl.startsWith('http') && !currentUrl.includes('browser://')) {
|
|
||||||
console.log('[DEBUG] Webview did-finish-load, recording:', currentUrl);
|
|
||||||
addToSiteHistory(currentUrl);
|
|
||||||
ipcRenderer.invoke('save-site-history-entry', currentUrl);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// catch any target="_blank" or window.open() calls and open them as new tabs
|
// catch any target="_blank" or window.open() calls and open them as new tabs
|
||||||
@@ -310,7 +307,7 @@ function navigate() {
|
|||||||
webview.src = resolved;
|
webview.src = resolved;
|
||||||
|
|
||||||
scheduleRenderTabs();
|
scheduleRenderTabs();
|
||||||
updateNavButtons();
|
scheduleUpdateNavButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) {
|
function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) {
|
||||||
@@ -340,30 +337,12 @@ function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) {
|
|||||||
|
|
||||||
webview.addEventListener('did-navigate', e => {
|
webview.addEventListener('did-navigate', e => {
|
||||||
handleNavigation(tabId, e.url);
|
handleNavigation(tabId, e.url);
|
||||||
if (e.url.startsWith('http')) {
|
|
||||||
addToSiteHistory(e.url);
|
|
||||||
ipcRenderer.invoke('save-site-history-entry', e.url).catch(err =>
|
|
||||||
console.error('Failed to save to file:', err)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
webview.addEventListener('did-navigate-in-page', e => {
|
webview.addEventListener('did-navigate-in-page', e => {
|
||||||
handleNavigation(tabId, e.url);
|
handleNavigation(tabId, e.url);
|
||||||
if (e.url.startsWith('http')) {
|
|
||||||
addToSiteHistory(e.url);
|
|
||||||
ipcRenderer.invoke('save-site-history-entry', e.url).catch(err =>
|
|
||||||
console.error('Failed to save to file:', err)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
webview.addEventListener('did-finish-load', () => {
|
webview.addEventListener('did-finish-load', () => {
|
||||||
const currentUrl = webview.getURL();
|
scheduleUpdateNavButtons();
|
||||||
if (currentUrl.startsWith('http') && !currentUrl.includes('browser://')) {
|
|
||||||
addToSiteHistory(currentUrl);
|
|
||||||
ipcRenderer.invoke('save-site-history-entry', currentUrl);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
webview.addEventListener('new-window', e => {
|
webview.addEventListener('new-window', e => {
|
||||||
@@ -393,7 +372,7 @@ function convertHomeTabToWebview(tabId, inputUrl, resolvedUrl) {
|
|||||||
if (homeContainer) homeContainer.classList.remove('active');
|
if (homeContainer) homeContainer.classList.remove('active');
|
||||||
webview.classList.add('active');
|
webview.classList.add('active');
|
||||||
|
|
||||||
updateNavButtons();
|
scheduleUpdateNavButtons();
|
||||||
// Activate converted webview tab and update UI
|
// Activate converted webview tab and update UI
|
||||||
setActiveTab(tabId);
|
setActiveTab(tabId);
|
||||||
scheduleRenderTabs();
|
scheduleRenderTabs();
|
||||||
@@ -403,7 +382,7 @@ function handleNavigation(tabId, newUrl) {
|
|||||||
const tab = tabs.find(t => t.id === tabId);
|
const tab = tabs.find(t => t.id === tabId);
|
||||||
if (!tab) return;
|
if (!tab) return;
|
||||||
|
|
||||||
console.log('[DEBUG] handleNavigation called with:', newUrl);
|
debug('[DEBUG] handleNavigation called with:', newUrl);
|
||||||
|
|
||||||
// --- record every real navigation into history ---
|
// --- record every real navigation into history ---
|
||||||
if (tab.history[tab.historyIndex] !== newUrl) {
|
if (tab.history[tab.historyIndex] !== newUrl) {
|
||||||
@@ -418,7 +397,7 @@ function handleNavigation(tabId, newUrl) {
|
|||||||
!newUrl.startsWith('file://') &&
|
!newUrl.startsWith('file://') &&
|
||||||
!newUrl.includes('browser://') &&
|
!newUrl.includes('browser://') &&
|
||||||
newUrl.startsWith('http')) {
|
newUrl.startsWith('http')) {
|
||||||
console.log('[DEBUG] Adding to site history:', newUrl);
|
debug('[DEBUG] Adding to site history:', newUrl);
|
||||||
addToSiteHistory(newUrl);
|
addToSiteHistory(newUrl);
|
||||||
// Also send to main process for file storage
|
// Also send to main process for file storage
|
||||||
ipcRenderer.invoke('save-site-history-entry', newUrl);
|
ipcRenderer.invoke('save-site-history-entry', newUrl);
|
||||||
@@ -440,7 +419,7 @@ function handleNavigation(tabId, newUrl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scheduleRenderTabs();
|
scheduleRenderTabs();
|
||||||
updateNavButtons();
|
scheduleUpdateNavButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -620,18 +599,19 @@ function goForward() {
|
|||||||
|
|
||||||
function updateNavButtons() {
|
function updateNavButtons() {
|
||||||
const webview = document.getElementById(`tab-${activeTabId}`);
|
const webview = document.getElementById(`tab-${activeTabId}`);
|
||||||
const backBtn = document.querySelector('.nav-left button:nth-child(1)');
|
if (!backBtnCached || !fwdBtnCached) {
|
||||||
const forwardBtn = document.querySelector('.nav-left button:nth-child(2)');
|
backBtnCached = document.querySelector('.nav-left button:nth-child(1)');
|
||||||
|
fwdBtnCached = document.querySelector('.nav-left button:nth-child(2)');
|
||||||
backBtn.disabled = !webview || !webview.canGoBack();
|
}
|
||||||
forwardBtn.disabled = !webview || !webview.canGoForward();
|
if (backBtnCached) backBtnCached.disabled = !webview || !webview.canGoBack();
|
||||||
|
if (fwdBtnCached) fwdBtnCached.disabled = !webview || !webview.canGoForward();
|
||||||
}
|
}
|
||||||
|
|
||||||
function reload() {
|
function reload() {
|
||||||
const webview = document.getElementById(`tab-${activeTabId}`);
|
const webview = document.getElementById(`tab-${activeTabId}`);
|
||||||
if (webview) {
|
if (webview) {
|
||||||
webview.reload();
|
webview.reload();
|
||||||
updateNavButtons(); // keep back/forward buttons in sync after a reload
|
scheduleUpdateNavButtons(); // keep back/forward buttons in sync after a reload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,6 +691,13 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
const forwardBtn = document.querySelector('.nav-left button:nth-child(2)');
|
const forwardBtn = document.querySelector('.nav-left button:nth-child(2)');
|
||||||
backBtn.addEventListener('click', goBack);
|
backBtn.addEventListener('click', goBack);
|
||||||
forwardBtn.addEventListener('click', goForward);
|
forwardBtn.addEventListener('click', goForward);
|
||||||
|
// cache for faster updates
|
||||||
|
backBtnCached = backBtn;
|
||||||
|
fwdBtnCached = forwardBtn;
|
||||||
|
|
||||||
|
// settings button
|
||||||
|
const settingsBtn = document.getElementById('open-settings-btn');
|
||||||
|
if (settingsBtn) settingsBtn.addEventListener('click', openSettings);
|
||||||
|
|
||||||
// window control bindings
|
// window control bindings
|
||||||
const minBtn = document.getElementById('min-btn');
|
const minBtn = document.getElementById('min-btn');
|
||||||
@@ -782,6 +769,7 @@ try {
|
|||||||
// 4) unify context-menu wiring
|
// 4) unify context-menu wiring
|
||||||
function showContextMenu(x,y) {
|
function showContextMenu(x,y) {
|
||||||
if (!contextMenu) return;
|
if (!contextMenu) return;
|
||||||
|
lastContextPos = { x, y };
|
||||||
contextMenu.style.top = `${y}px`;
|
contextMenu.style.top = `${y}px`;
|
||||||
contextMenu.style.left = `${x}px`;
|
contextMenu.style.left = `${x}px`;
|
||||||
contextMenu.classList.add('visible');
|
contextMenu.classList.add('visible');
|
||||||
@@ -793,38 +781,40 @@ document.addEventListener('contextmenu', e => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.addEventListener('click', ()=> contextMenu && contextMenu.classList.remove('visible'));
|
document.addEventListener('click', ()=> contextMenu && contextMenu.classList.remove('visible'));
|
||||||
menuItems.forEach(item => item.addEventListener('click', async evt => {
|
if (remote && fs) {
|
||||||
const action = item.dataset.action;
|
menuItems.forEach(item => item.addEventListener('click', async evt => {
|
||||||
const win = remote.getCurrentWindow();
|
const action = item.dataset.action;
|
||||||
|
const win = remote.getCurrentWindow();
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'save-page': {
|
case 'save-page': {
|
||||||
const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'page.html' });
|
const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'page.html' });
|
||||||
if (!canceled && filePath) win.webContents.savePage(filePath, 'HTMLComplete');
|
if (!canceled && filePath) win.webContents.savePage(filePath, 'HTMLComplete');
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
case 'select-all':
|
||||||
|
document.execCommand('selectAll');
|
||||||
|
break;
|
||||||
|
case 'screenshot': {
|
||||||
|
const image = await win.webContents.capturePage();
|
||||||
|
const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'screenshot.png' });
|
||||||
|
if (!canceled && filePath) fs.writeFileSync(filePath, image.toPNG());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'view-source': {
|
||||||
|
const html = document.documentElement.outerHTML;
|
||||||
|
const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'source.html' });
|
||||||
|
if (!canceled && filePath) fs.writeFileSync(filePath, html);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'inspect-accessibility':
|
||||||
|
win.webContents.inspectAccessibilityNode(lastContextPos.x, lastContextPos.y);
|
||||||
|
break;
|
||||||
|
case 'inspect-element':
|
||||||
|
win.webContents.inspectElement(lastContextPos.x, lastContextPos.y);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case 'select-all':
|
|
||||||
document.execCommand('selectAll');
|
|
||||||
break;
|
|
||||||
case 'screenshot': {
|
|
||||||
const image = await win.webContents.capturePage();
|
|
||||||
const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'screenshot.png' });
|
|
||||||
if (!canceled && filePath) fs.writeFileSync(filePath, image.toPNG());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'view-source': {
|
|
||||||
const html = document.documentElement.outerHTML;
|
|
||||||
const { canceled, filePath } = await remote.dialog.showSaveDialog(win, { defaultPath: 'source.html' });
|
|
||||||
if (!canceled && filePath) fs.writeFileSync(filePath, html);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'inspect-accessibility':
|
|
||||||
win.webContents.inspectAccessibilityNode(e.clientX, e.clientY);
|
|
||||||
break;
|
|
||||||
case 'inspect-element':
|
|
||||||
win.webContents.inspectElement(e.clientX, e.clientY);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
contextMenu.classList.remove('visible');
|
contextMenu.classList.remove('visible');
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
|
|||||||
+1
-13
@@ -74,10 +74,8 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// Tabs: simple controller
|
// Tabs: simple controller
|
||||||
function activateTab(tabName) {
|
function activateTab(tabName) {
|
||||||
console.log('[TABS] Activating tab:', tabName);
|
|
||||||
const links = document.querySelectorAll('.tab-link');
|
const links = document.querySelectorAll('.tab-link');
|
||||||
const panels = document.querySelectorAll('.tab-panel');
|
const panels = document.querySelectorAll('.tab-panel');
|
||||||
console.log('[TABS] Found', links.length, 'tab links and', panels.length, 'panels');
|
|
||||||
|
|
||||||
links.forEach(l => {
|
links.forEach(l => {
|
||||||
const isActive = l.dataset.tab === tabName;
|
const isActive = l.dataset.tab === tabName;
|
||||||
@@ -89,21 +87,17 @@ function activateTab(tabName) {
|
|||||||
const isActive = p.id === `panel-${tabName}`;
|
const isActive = p.id === `panel-${tabName}`;
|
||||||
p.classList.toggle('active', isActive);
|
p.classList.toggle('active', isActive);
|
||||||
p.hidden = !isActive;
|
p.hidden = !isActive;
|
||||||
console.log('[TABS] Panel', p.id, 'active:', isActive);
|
// noop
|
||||||
});
|
});
|
||||||
try { localStorage.setItem(TAB_STORAGE_KEY, tabName); } catch {}
|
try { localStorage.setItem(TAB_STORAGE_KEY, tabName); } catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initTabs() {
|
function initTabs() {
|
||||||
console.log('[TABS] Initializing tabs...');
|
|
||||||
const links = document.querySelectorAll('.tab-link');
|
const links = document.querySelectorAll('.tab-link');
|
||||||
console.log('[TABS] Found tab links:', links.length);
|
|
||||||
|
|
||||||
// Direct listeners (for accessibility focus handling)
|
// Direct listeners (for accessibility focus handling)
|
||||||
links.forEach((link, index) => {
|
links.forEach((link, index) => {
|
||||||
console.log('[TABS] Setting up listener for tab', index, 'with data-tab:', link.dataset.tab);
|
|
||||||
link.addEventListener('click', (e) => {
|
link.addEventListener('click', (e) => {
|
||||||
console.log('[TABS] Tab clicked:', link.dataset.tab);
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const name = link.dataset.tab;
|
const name = link.dataset.tab;
|
||||||
@@ -118,12 +112,9 @@ function initTabs() {
|
|||||||
// Delegation as a fallback if elements are re-rendered
|
// Delegation as a fallback if elements are re-rendered
|
||||||
const tabContainer = document.querySelector('.tabs');
|
const tabContainer = document.querySelector('.tabs');
|
||||||
if (tabContainer) {
|
if (tabContainer) {
|
||||||
console.log('[TABS] Setting up delegation on tabs container');
|
|
||||||
tabContainer.addEventListener('click', (e) => {
|
tabContainer.addEventListener('click', (e) => {
|
||||||
console.log('[TABS] Container click detected');
|
|
||||||
const btn = e.target && e.target.closest ? e.target.closest('.tab-link') : null;
|
const btn = e.target && e.target.closest ? e.target.closest('.tab-link') : null;
|
||||||
if (!btn || !tabContainer.contains(btn)) return;
|
if (!btn || !tabContainer.contains(btn)) return;
|
||||||
console.log('[TABS] Delegated click for tab:', btn.dataset.tab);
|
|
||||||
const name = btn.dataset.tab;
|
const name = btn.dataset.tab;
|
||||||
if (!name) return;
|
if (!name) return;
|
||||||
if (location.hash !== `#${name}`) {
|
if (location.hash !== `#${name}`) {
|
||||||
@@ -139,13 +130,10 @@ function initTabs() {
|
|||||||
try { initial = localStorage.getItem(TAB_STORAGE_KEY) || null; } catch {}
|
try { initial = localStorage.getItem(TAB_STORAGE_KEY) || null; } catch {}
|
||||||
}
|
}
|
||||||
if (!initial) initial = 'general';
|
if (!initial) initial = 'general';
|
||||||
console.log('[TABS] Initial tab:', initial);
|
|
||||||
activateTab(initial);
|
activateTab(initial);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize tabs after DOM is ready but before customization init uses the DOM
|
// Initialize tabs after DOM is ready but before customization init uses the DOM
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
console.log('[TABS] DOM loaded, initializing tabs...');
|
|
||||||
initTabs();
|
initTabs();
|
||||||
console.log('[TABS] Tabs initialized');
|
|
||||||
});
|
});
|
||||||
|
|||||||
+22
-12
@@ -28,7 +28,8 @@ html, body {
|
|||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
background: #1e1e2e;
|
background: #1e1e2e;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
/* flatter header to reduce paint cost */
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-left,
|
.nav-left,
|
||||||
@@ -72,7 +73,7 @@ html, body {
|
|||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s;
|
transition: background 120ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav button:hover {
|
#nav button:hover {
|
||||||
@@ -179,7 +180,8 @@ html, body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
height: 28px; /* reduce overall tab height */
|
height: 28px; /* reduce overall tab height */
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
background: linear-gradient(180deg, #24262b 0%, #1f2025 100%);
|
/* use flat background to avoid gradient paints */
|
||||||
|
background: #24262b;
|
||||||
border: 1px solid #2b2d34;
|
border: 1px solid #2b2d34;
|
||||||
border-bottom: none; /* let it visually merge with the strip line */
|
border-bottom: none; /* let it visually merge with the strip line */
|
||||||
border-radius: 6px 6px 0 0; /* slightly tighter radius */
|
border-radius: 6px 6px 0 0; /* slightly tighter radius */
|
||||||
@@ -189,17 +191,16 @@ html, body {
|
|||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
flex: 0 1 180px; /* like Chrome: shrink when crowded */
|
flex: 0 1 180px; /* like Chrome: shrink when crowded */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: background .15s ease, color .15s ease, box-shadow .15s ease;
|
transition: background 120ms ease, color 120ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab:hover {
|
.tab:hover { background: #2a2c33; }
|
||||||
background: linear-gradient(180deg, #2a2c33 0%, #23252b 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab.active {
|
.tab.active {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: linear-gradient(180deg, #2f323a 0%, #2a2c33 100%);
|
background: #2f323a;
|
||||||
box-shadow: 0 1px 0 rgba(255,255,255,0.04) inset, 0 0 0 1px #3a3d46 inset;
|
/* remove expensive inner shadows */
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab .tab-favicon {
|
.tab .tab-favicon {
|
||||||
@@ -228,7 +229,7 @@ html, body {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
color: #b5b5b5;
|
color: #b5b5b5;
|
||||||
opacity: 0; /* hidden by default */
|
opacity: 0; /* hidden by default */
|
||||||
transition: background .15s ease, color .15s ease, opacity .15s ease;
|
transition: background 120ms ease, color 120ms ease, opacity 120ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab:hover .tab-close,
|
.tab:hover .tab-close,
|
||||||
@@ -249,7 +250,7 @@ html, body {
|
|||||||
background: #23252b;
|
background: #23252b;
|
||||||
color: #d5d5d5;
|
color: #d5d5d5;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background .15s ease, color .15s ease, border-color .15s ease;
|
transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
|
||||||
}
|
}
|
||||||
.new-tab-button:hover { background: #2b2d34; color: #fff; border-color: #3a3d46; }
|
.new-tab-button:hover { background: #2b2d34; color: #fff; border-color: #3a3d46; }
|
||||||
.new-tab-button:active { background: #262830; }
|
.new-tab-button:active { background: #262830; }
|
||||||
@@ -301,7 +302,7 @@ html, body {
|
|||||||
color: white;
|
color: white;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s;
|
transition: background 120ms ease;
|
||||||
}
|
}
|
||||||
#window-controls button:hover {
|
#window-controls button:hover {
|
||||||
background: rgba(255,255,255,0.1);
|
background: rgba(255,255,255,0.1);
|
||||||
@@ -324,3 +325,12 @@ html, body {
|
|||||||
#tab-bar::-webkit-scrollbar-thumb:hover {
|
#tab-bar::-webkit-scrollbar-thumb:hover {
|
||||||
background: #555;
|
background: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Respect reduced motion preferences */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
* {
|
||||||
|
animation: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user