0.3.34: greyer chrome bar, tighter bookmarks + drag-reorder, legible Tor/Theseus chips in light mode

Chrome strip: light-mode --bg/--surface/--surface2/--active retuned to a
soft neutral grey. Flat #ffffff tabs disappeared into light Windows
title bars and the top strip read as one bright rectangle; now the
tab row sits on #e6e8ec, inactive tabs are #f2f4f7, active tab is
white so the current one still pops.

Bookmarks: chips down to 130px max-width, 11px text, 12px favicon,
tighter padding, 22px row (was 26). Fits ~40% more saved pages in
the same width. Chips are now draggable — dragstart/dragover/drop
mirror the tab strip's reorder pattern, dropping before/after by
which half of the target chip the pointer is on. IPC bookmark-move
splices the array; a missing entry or self-drop is a no-op.

Tor + Theseus (⛓ logo) chips in light mode: white-on-#253A49 was
below legible at 12-13px on real light Windows themes. Both now use
#eef1f5 with #253A49 ink and a subtle border — same footprint, dark
letters read cleanly. .tor.connecting/.on keep amber/purple hue but
in light-appropriate fills with dark text. .upchip picks up the
same treatment for consistency.
This commit is contained in:
Local Dev 2026-09-08 20:34:21 +02:00
parent 4c01ec7b7d
commit fa65ce5bab
4 changed files with 85 additions and 18 deletions

View file

@ -13,7 +13,12 @@
} }
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
:root { :root {
--bg:#e9ecf1; --surface:#ffffff; --surface2:#f1f3f7; --active:#dbe1ec; /* Chrome strip is a soft neutral grey — flat white was too bright next
to the browsed page, and pure #ffffff tabs disappeared into the
window title bar on light Windows themes. --bg tints the whole
top strip; --surface (inactive tab) sits a hair above it; --active
(foreground tab) pops a bit brighter so the current tab reads. */
--bg:#e6e8ec; --surface:#f2f4f7; --surface2:#eaecf0; --active:#ffffff;
--ink:#1a1f28; --mut:#3c4453; --dim:#697280; --faint:#98a1b0; --ink:#1a1f28; --mut:#3c4453; --dim:#697280; --faint:#98a1b0;
--line:rgba(0,0,0,.18); --line2:rgba(0,0,0,.11); --hover:rgba(0,0,0,.06); --line:rgba(0,0,0,.18); --line2:rgba(0,0,0,.11); --hover:rgba(0,0,0,.06);
/* Darker acid for light backgrounds — same hue family, ~5.5:1 /* Darker acid for light backgrounds — same hue family, ~5.5:1
@ -293,8 +298,10 @@
padding: 6px 12px; border: 1px solid color-mix(in srgb, var(--acid) 20%, transparent); border-radius: 8px; cursor: pointer; background: rgb(from var(--acid) r g b / .08); font-size: 13px; } padding: 6px 12px; border: 1px solid color-mix(in srgb, var(--acid) 20%, transparent); border-radius: 8px; cursor: pointer; background: rgb(from var(--acid) r g b / .08); font-size: 13px; }
.logo:hover { background: rgb(from var(--acid) r g b / .16); } .logo:hover { background: rgb(from var(--acid) r g b / .16); }
.logo .gear { font-size: 12px; opacity: .8; } .logo .gear { font-size: 12px; opacity: .8; }
/* favorites bar — new-tab page only, single compact row */ /* favorites bar — new-tab page only, single compact row. Chips are
.bookmarks { display: flex; align-items: center; gap: 4px; padding: 3px 10px 5px; height: 26px; overflow: hidden; } tight so many fit in the row; the whole strip is 22px tall (down
from 26) so the chrome view gains a bit of vertical space. */
.bookmarks { display: flex; align-items: center; gap: 3px; padding: 2px 10px 4px; height: 22px; overflow: hidden; }
/* Ctrl+F find-in-page bar. Sits below the address bar (above tabs body), /* Ctrl+F find-in-page bar. Sits below the address bar (above tabs body),
~32px tall so it's part of the chrome view height like .tordisc / .bcnrbar. ~32px tall so it's part of the chrome view height like .tordisc / .bcnrbar.
syncHeight in the tabs handler picks it up automatically. */ syncHeight in the tabs handler picks it up automatically. */
@ -307,11 +314,14 @@
.findbar .findbtn { border: 1px solid var(--line); background: var(--surface2); color: var(--ink); border-radius: 6px; .findbar .findbtn { border: 1px solid var(--line); background: var(--surface2); color: var(--ink); border-radius: 6px;
padding: 3px 8px; font-size: 13px; cursor: pointer; } padding: 3px 8px; font-size: 13px; cursor: pointer; }
.findbar .findbtn:hover { background: var(--surface); border-color: color-mix(in srgb, var(--acid) 35%, transparent); } .findbar .findbtn:hover { background: var(--surface); border-color: color-mix(in srgb, var(--acid) 35%, transparent); }
.bm { display: flex; align-items: center; gap: 6px; max-width: 180px; padding: 3px 9px; border-radius: 6px; .bm { display: flex; align-items: center; gap: 5px; max-width: 130px; padding: 2px 7px; border-radius: 5px;
background: var(--surface2); border: 1px solid var(--hover); color: var(--mut); font-size: 12px; cursor: pointer; white-space: nowrap; } background: var(--surface2); border: 1px solid var(--hover); color: var(--mut); font-size: 11px; cursor: grab; white-space: nowrap; }
.bm:hover { background: var(--active); color: var(--ink); } .bm:hover { background: var(--active); color: var(--ink); }
.bm .bmfav { width: 14px; height: 14px; flex: none; object-fit: contain; border-radius: 2px; } .bm.dragging { opacity: .45; cursor: grabbing; }
.bm .bt { overflow: hidden; text-overflow: ellipsis; } .bm.dropbefore { box-shadow: -2px 0 0 var(--acid); }
.bm.dropafter { box-shadow: 2px 0 0 var(--acid); }
.bm .bmfav { width: 12px; height: 12px; flex: none; object-fit: contain; border-radius: 2px; }
.bm .bt { overflow: hidden; text-overflow: ellipsis; max-width: 96px; }
/* In-chrome prompt modal (window.prompt is disabled in Electron BrowserViews, /* In-chrome prompt modal (window.prompt is disabled in Electron BrowserViews,
so bookmark rename etc. use this instead). */ so bookmark rename etc. use this instead). */
.promptmask { position: fixed; inset: 0; background: rgba(0,0,0,.45); z-index: 10000; .promptmask { position: fixed; inset: 0; background: rgba(0,0,0,.45); z-index: 10000;
@ -344,15 +354,21 @@
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
.ctxmenu { background: #ffffff; border-color: rgba(0,0,0,.15); } .ctxmenu { background: #ffffff; border-color: rgba(0,0,0,.15); }
.ctxmenu .mi:hover { background: rgba(0,0,0,.05); } .ctxmenu .mi:hover { background: rgba(0,0,0,.05); }
/* BCH dark #253A49 as a light-mode accent so the toolbar isn't /* The old light-mode chips used the BCH dark #253A49 as their fill.
all-white-on-white. .logo (⛓ Theseus) becomes a dark chip; the At small font sizes on light Windows the white-on-dark-navy label
download / update pill picks it up so both stand out. */ fell below legible, so both .logo (⛓ Theseus) and .tor switch to
.logo { background: #253A49; color: #F8FDFF; border-color: #253A49; } a light-grey chip with dark ink — same footprint, readable text.
.logo:hover { background: #1a2c39; } Update pill picks up the same treatment. */
.logo .gear { color: #F8FDFF; opacity: .85; } .logo { background: #eef1f5; color: #253A49; border-color: rgba(0,0,0,.14); }
.upchip { background: #253A49; color: #F8FDFF; border-color: #253A49; } .logo:hover { background: #e2e6ec; }
.upchip .upcore:hover { background: #1a2c39; } .logo .gear { color: #253A49; opacity: .85; }
.upchip .updismiss { border-left-color: rgba(255,255,255,.20); color: #F8FDFF; } .tor { background: #eef1f5; color: #253A49; border-color: rgba(0,0,0,.14); }
.tor:hover { background: #e2e6ec; }
.tor.connecting { background: #fbe9b5; color: #5a3f00; border-color: rgba(0,0,0,.14); }
.tor.on { background: #e6d9f5; color: #3f1f6b; border-color: rgba(0,0,0,.14); }
.upchip { background: #eef1f5; color: #253A49; border-color: rgba(0,0,0,.14); }
.upchip .upcore:hover { background: #e2e6ec; }
.upchip .updismiss { border-left-color: rgba(0,0,0,.14); color: #253A49; }
} }
.tordisc { font-size: 11.5px; color: #d9c7f2; background: #2a1c40; border-top: 1px solid #6b3fa055; padding: 5px 14px; } .tordisc { font-size: 11.5px; color: #d9c7f2; background: #2a1c40; border-top: 1px solid #6b3fa055; padding: 5px 14px; }
.tordisc a { color: var(--acid); } .tordisc a { color: var(--acid); }
@ -793,7 +809,7 @@
const fav = b.favicon const fav = b.favicon
? `<img class="bmfav" src="${String(b.favicon).replace(/"/g,"&quot;")}" onerror="this.remove()">` ? `<img class="bmfav" src="${String(b.favicon).replace(/"/g,"&quot;")}" onerror="this.remove()">`
: `<span class="bmfav" style="display:inline-block"></span>`; : `<span class="bmfav" style="display:inline-block"></span>`;
return `<div class="bm" data-i="${i}" title="${(b.url||"").replace(/"/g,"&quot;")}">${fav}<span class="bt">${(b.title||b.url).replace(/</g,"&lt;")}</span><span class="bx" data-x="${i}"></span></div>`; return `<div class="bm" draggable="true" data-i="${i}" data-url="${(b.url||"").replace(/"/g,"&quot;")}" title="${((b.title||b.url)+" "+(b.url||"")).replace(/"/g,"&quot;")}">${fav}<span class="bt">${(b.title||b.url).replace(/</g,"&lt;")}</span><span class="bx" data-x="${i}"></span></div>`;
}).join("") }).join("")
: `<span class="bm-empty">No saved pages yet — click ☆ or right-click here to add one.</span>`; : `<span class="bm-empty">No saved pages yet — click ☆ or right-click here to add one.</span>`;
box.querySelectorAll(".bm").forEach((el) => el.onclick = (e) => { box.querySelectorAll(".bm").forEach((el) => el.onclick = (e) => {
@ -801,6 +817,40 @@
if (e.target.dataset.x !== undefined) T.removeBookmark(bookmarks[i].url); if (e.target.dataset.x !== undefined) T.removeBookmark(bookmarks[i].url);
else T.navigate(bookmarks[i].url); else T.navigate(bookmarks[i].url);
}); });
// Drag-reorder chips — same shape as the tab strip. Drop side is chosen
// by whether the pointer is on the left/right half of the target chip.
// dragUrl is the identity we hand to bookmark-move; renderBookmarks
// fires again after the reorder, which rebinds these handlers cleanly.
let dragUrl = null;
box.querySelectorAll(".bm").forEach((row) => {
row.addEventListener("dragstart", (e) => {
dragUrl = row.dataset.url;
try { e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", dragUrl); } catch {}
row.classList.add("dragging");
});
row.addEventListener("dragend", () => {
row.classList.remove("dragging");
box.querySelectorAll(".bm").forEach((r) => r.classList.remove("dropbefore", "dropafter"));
dragUrl = null;
});
row.addEventListener("dragover", (e) => {
if (!dragUrl || row.dataset.url === dragUrl) return;
e.preventDefault(); e.dataTransfer.dropEffect = "move";
const r = row.getBoundingClientRect();
const before = (e.clientX - r.left) < r.width / 2;
row.classList.toggle("dropbefore", before);
row.classList.toggle("dropafter", !before);
});
row.addEventListener("dragleave", () => row.classList.remove("dropbefore", "dropafter"));
row.addEventListener("drop", (e) => {
e.preventDefault();
const targetUrl = row.dataset.url;
if (!dragUrl || dragUrl === targetUrl) return;
const r = row.getBoundingClientRect();
const before = (e.clientX - r.left) < r.width / 2;
T.moveBookmark && T.moveBookmark(dragUrl, targetUrl, before ? "before" : "after");
});
});
// Backfill: older bookmarks stored before favicon-support have no icon. // Backfill: older bookmarks stored before favicon-support have no icon.
// If we're currently on that URL and have a favicon in hand, patch it in // If we're currently on that URL and have a favicon in hand, patch it in
// once so the next render draws it. // once so the next render draws it.

16
main.js
View file

@ -3772,6 +3772,22 @@ ipcMain.handle("bookmark-remove", (_e, url) => {
saveBookmarks(); emitBookmarks(); saveBookmarks(); emitBookmarks();
return bookmarks; return bookmarks;
}); });
// Reorder: pull `fromUrl` out of the list and reinsert it before or after
// `targetUrl`. Renderer picks the side by which half of the target chip the
// pointer is on, same convention the tab strip uses. A missing entry or a
// self-drop is a no-op, so noisy drag events don't corrupt the list.
ipcMain.handle("bookmark-move", (_e, fromUrl, targetUrl, place) => {
if (typeof fromUrl !== "string" || typeof targetUrl !== "string" || fromUrl === targetUrl) return bookmarks;
const from = bookmarks.findIndex((b) => b.url === fromUrl);
if (from < 0) return bookmarks;
const [moved] = bookmarks.splice(from, 1);
let to = bookmarks.findIndex((b) => b.url === targetUrl);
if (to < 0) { bookmarks.splice(from, 0, moved); return bookmarks; }
if (place === "after") to += 1;
bookmarks.splice(to, 0, moved);
saveBookmarks(); emitBookmarks();
return bookmarks;
});
ipcMain.handle("settings-get", () => settings); ipcMain.handle("settings-get", () => settings);
ipcMain.handle("settings-set", (_e, key, val) => { ipcMain.handle("settings-set", (_e, key, val) => {
if (key in SETTINGS_DEFAULTS) { settings[key] = val; saveSettings(); } if (key in SETTINGS_DEFAULTS) { settings[key] = val; saveSettings(); }

View file

@ -1,6 +1,6 @@
{ {
"name": "theseus-navigator", "name": "theseus-navigator",
"version": "0.3.33", "version": "0.3.34",
"description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.", "description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.",
"author": "Silent Mode", "author": "Silent Mode",
"main": "main.js", "main": "main.js",

View file

@ -48,6 +48,7 @@ contextBridge.exposeInMainWorld("theseus", {
addBookmark: (bm) => ipcRenderer.invoke("bookmark-add", bm), addBookmark: (bm) => ipcRenderer.invoke("bookmark-add", bm),
updateBookmark: (url, patch) => ipcRenderer.invoke("bookmark-update", url, patch), updateBookmark: (url, patch) => ipcRenderer.invoke("bookmark-update", url, patch),
removeBookmark: (url) => ipcRenderer.invoke("bookmark-remove", url), removeBookmark: (url) => ipcRenderer.invoke("bookmark-remove", url),
moveBookmark: (fromUrl, targetUrl, place) => ipcRenderer.invoke("bookmark-move", fromUrl, targetUrl, place),
onBookmarks: (cb) => ipcRenderer.on("bookmarks", (_e, d) => cb(d)), onBookmarks: (cb) => ipcRenderer.on("bookmarks", (_e, d) => cb(d)),
onNav: (cb) => ipcRenderer.on("nav", (_e, d) => cb(d)), onNav: (cb) => ipcRenderer.on("nav", (_e, d) => cb(d)),
onTor: (cb) => ipcRenderer.on("tor", (_e, d) => cb(d)), onTor: (cb) => ipcRenderer.on("tor", (_e, d) => cb(d)),