theseus/chrome.html
Local Dev ad34ad3bbe Theseus: per-engine symbols + light-theme cascade fix
- Each search engine shows a symbol in the toolbar dropdown, the placeholder,
  and Settings (🦆 DuckDuckGo, 🦁 Brave, 🔵 Google, 🔎 Bing, 🛡️ Startpage,
  🔴 Yandex); custom engines take an optional symbol (defaults to 🔍).
- Fix: light-theme @media blocks for settings/popover/home were placed before
  the base rules and lost the cascade (sidebar stayed dark) — moved them last.
2026-07-30 22:09:26 +02:00

280 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html>
<head><meta charset="utf-8">
<style>
:root {
color-scheme: light dark; font-family: system-ui, sans-serif;
--bg:#0f1420; --surface:#1b2330; --surface2:#171e2a; --active:#263041cc;
--ink:#e7eaf1; --mut:#b9c2d0; --dim:#8b98a9; --faint:#5e6678;
--line:rgba(255,255,255,.20); --line2:rgba(255,255,255,.13); --hover:rgba(255,255,255,.08);
}
@media (prefers-color-scheme: light) {
:root {
--bg:#e9ecf1; --surface:#ffffff; --surface2:#f1f3f7; --active:#dbe1ec;
--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);
}
}
[hidden] { display: none !important; }
body { margin: 0; height: 100vh; background: var(--bg); color: var(--ink); user-select: none; overflow: hidden; }
.tabs { display: flex; align-items: flex-end; gap: 4px; padding: 6px 8px 0; height: 34px; }
.tab { display: flex; align-items: center; gap: 8px; max-width: 200px; min-width: 90px; padding: 6px 10px;
background: var(--surface); border: 1px solid var(--line2); border-bottom: none; border-radius: 8px 8px 0 0;
font-size: 12.5px; cursor: default; color: var(--mut); }
.tab.active { background: var(--active); color: var(--ink); }
.tab .t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.tab .spin { width: 10px; height: 10px; flex: none; border: 1.5px solid #ffffff2e; border-top-color: #d6ff3d; border-radius: 50%; animation: spin .7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.tab .x { opacity: .5; cursor: pointer; padding: 0 2px; border-radius: 4px; }
.tab .x:hover { opacity: 1; background: var(--line2); }
.newtab { padding: 4px 10px; cursor: pointer; color: var(--dim); border-radius: 6px; font-size: 16px; }
.newtab:hover { background: var(--hover); color: var(--ink); }
.bar { display: flex; gap: 6px; align-items: center; padding: 6px 10px; }
.nav { display: flex; gap: 1px; }
.ic { width: 34px; height: 32px; display: grid; place-items: center; border-radius: 8px; cursor: pointer;
color: var(--mut); background: transparent; border: none; padding: 0; }
.ic svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.ic:hover { background: var(--line2); color: var(--ink); }
.ic:active { background: var(--line); }
.ic:disabled { opacity: .28; cursor: default; background: transparent; }
/* indeterminate loading bar under the toolbar */
.loadbar { height: 2px; overflow: hidden; background: transparent; }
.loadbar.on::after { content: ""; display: block; height: 100%; width: 35%; border-radius: 2px;
background: linear-gradient(90deg, transparent, #4b7bec, #d6ff3d, transparent); animation: loadslide 1.1s infinite linear; }
@keyframes loadslide { 0% { transform: translateX(-110%); } 100% { transform: translateX(400%); } }
/* address bar */
.urlwrap { flex: 1; display: flex; align-items: center; gap: 4px; background: var(--surface); border: 1px solid var(--line);
border-radius: 999px; padding: 0 6px 0 4px; }
.urlwrap:focus-within { border-color: #4b7bec; }
/* Firefox-style padlock — always present (the "site security" button) */
.secbadge { display: grid; place-items: center; width: 28px; height: 26px; border-radius: 6px; border: none;
background: transparent; cursor: pointer; padding: 0; }
.secbadge:hover { background: var(--line2); }
.secbadge svg { width: 14px; height: 14px; }
.secbadge svg .shk { fill: none; stroke: var(--dim); stroke-width: 1.3; }
.secbadge svg .bdy { fill: var(--dim); }
.secbadge.warn svg .shk { stroke: #f6ad55; } .secbadge.warn svg .bdy { fill: #f6ad55; }
input { padding: 7px 6px; border-radius: 999px; border: none; background: transparent; color: inherit; font-size: 13.5px; outline: none; }
#url { flex: 1; }
/* minimal registry indicator at the END of the address bar */
.reg { font-size: 10.5px; letter-spacing: .03em; padding: 2px 9px; border-radius: 999px; white-space: nowrap;
background: rgba(214,255,61,.13); color: #d6ff3d; border: 1px solid #d6ff3d33; }
.reg.icann { background: rgba(139,152,169,.14); color: #9aa6b6; border-color: var(--line2); }
.star { border: none; background: transparent; cursor: pointer; color: var(--dim); font-size: 15px; padding: 2px 4px; border-radius: 6px; }
.star:hover { background: var(--line2); color: var(--ink); } .star.on { color: #ffd23d; }
/* search box: engine icon (click = pick engine) + a wide typing area */
.searchbox { display: flex; align-items: center; width: 300px; background: var(--surface); border: 1px solid var(--line);
border-radius: 999px; padding: 0 12px 0 3px; }
.searchbox:focus-within { border-color: #4b7bec; }
.engine-sel { appearance: none; -webkit-appearance: none; width: 34px; height: 30px; border: none; cursor: pointer;
color: transparent; border-radius: 999px 0 0 999px;
background: transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%238b98a9' stroke-width='1.6'%3E%3Ccircle cx='7' cy='7' r='4.2'/%3E%3Cpath d='M10.2 10.2 L14 14'/%3E%3C/svg%3E") no-repeat center / 15px; }
.engine-sel:hover { background-color: var(--hover); }
.engine-sel option { color: var(--ink); background: var(--surface); }
#search { flex: 1; padding: 7px 4px; }
.tor { padding: 7px 10px; border-radius: 8px; border: 1px solid var(--line); background: #2b2f3a; color: var(--ink); cursor: pointer; white-space: nowrap; font-size: 12.5px; }
.tor.connecting { background: #7a5c14; } .tor.on { background: #6b3fa0; }
.logo { display: flex; align-items: center; gap: 6px; font-weight: 700; color: #d6ff3d; white-space: nowrap;
padding: 6px 12px; border: 1px solid #d6ff3d33; border-radius: 8px; cursor: pointer; background: rgba(214,255,61,.08); font-size: 13px; }
.logo:hover { background: rgba(214,255,61,.16); }
.logo .gear { font-size: 12px; opacity: .8; }
/* favorites bar — new-tab page only, single compact row */
.bookmarks { display: flex; align-items: center; gap: 4px; padding: 3px 10px 5px; height: 26px; overflow: hidden; }
.bm { display: flex; align-items: center; gap: 6px; max-width: 180px; padding: 3px 9px; border-radius: 6px;
background: var(--surface2); border: 1px solid var(--hover); color: var(--mut); font-size: 12px; cursor: pointer; white-space: nowrap; }
.bm:hover { background: var(--active); color: var(--ink); }
.bm .bt { overflow: hidden; text-overflow: ellipsis; }
.bm .bx { opacity: 0; cursor: pointer; font-size: 11px; } .bm:hover .bx { opacity: .55; }
.bm .bx:hover { opacity: 1; color: #f6768a; }
.bm-empty { color: var(--faint); font-size: 11.5px; white-space: nowrap; }
.tordisc { font-size: 11.5px; color: #d9c7f2; background: #2a1c40; border-top: 1px solid #6b3fa055; padding: 5px 14px; }
.tordisc a { color: #d6ff3d; }
.bcnrbar { display: flex; align-items: center; gap: 10px; font-size: 11.5px; color: var(--mut);
background: #1a2417; border-top: 1px solid #d6ff3d33; padding: 6px 14px; }
.bcnrbar .breg { font-size: 10px; letter-spacing: .03em; padding: 1px 7px; border-radius: 999px;
background: rgba(214,255,61,.14); color: #d6ff3d; border: 1px solid #d6ff3d33; }
.bcnrbar b { color: var(--ink); }
.bcnrbar .bopen { margin-left: auto; padding: 4px 11px; border-radius: 7px; border: 1px solid #d6ff3d55;
background: rgba(214,255,61,.12); color: #eaffb0; cursor: pointer; font-size: 11.5px; }
.bcnrbar .bopen:hover { background: rgba(214,255,61,.22); }
.bcnrbar .bdismiss { color: var(--dim); text-decoration: none; } .bcnrbar .bdismiss:hover { color: var(--ink); }
</style></head>
<body>
<div class="tabs" id="tabs"></div>
<div class="bar">
<div class="nav">
<button class="ic" id="back" title="Back"><svg viewBox="0 0 16 16"><path d="M10 3 L5 8 L10 13"/></svg></button>
<button class="ic" id="fwd" title="Forward"><svg viewBox="0 0 16 16"><path d="M6 3 L11 8 L6 13"/></svg></button>
<button class="ic" id="reload" title="Reload"><svg viewBox="0 0 16 16"><path d="M13 8 a5 5 0 1 1 -1.5 -3.6"/><path d="M13 2.5 L13 5 L10.5 5"/></svg></button>
<button class="ic" id="home" title="Home"><svg viewBox="0 0 16 16"><path d="M2.5 7.5 L8 3 L13.5 7.5"/><path d="M4 6.7 L4 13 L12 13 L12 6.7"/></svg></button>
</div>
<div class="urlwrap">
<button class="secbadge" id="secbadge" title="Site information">
<svg viewBox="0 0 16 16" aria-hidden="true"><path class="shk" d="M5 7 V5.3 a3 3 0 0 1 6 0 V7"/><rect class="bdy" x="3.6" y="6.9" width="8.8" height="6" rx="1.2"/></svg>
</button>
<input id="url" placeholder="Ask a search engine or enter web address" spellcheck="false">
<span class="reg" id="reg" hidden></span>
<button class="star" id="star" title="Save this page"></button>
</div>
<div class="searchbox">
<select id="engine" class="engine-sel" title="Choose search engine"></select>
<input id="search" placeholder="Search" spellcheck="false">
</div>
<button class="tor" id="tor" title="Route traffic through Tor">🧅 Tor: Off</button>
<button class="logo" id="logo" title="Settings"><span>⛓ Theseus</span><span class="gear"></span></button>
</div>
<div class="loadbar" id="loadbar"></div>
<div class="bookmarks" id="bookmarks" hidden></div>
<div id="tordisc" class="tordisc" hidden>
Onion mode hides your IP from sites and your ISP (including .bch lookups).
It is <b>not full anonymity</b> — this browser can still be fingerprinted; for that use the Tor Browser.
<a href="#" id="tordismiss">got it</a>
</div>
<div id="bcnrbar" class="bcnrbar" hidden>
<span class="breg" id="bcnrreg">BCNR</span>
<span><b id="bcnrhost"></b> also exists on the <b>BCNR</b> decentralized registry.</span>
<button id="bcnropen" class="bopen">Open on BCNR</button>
<a href="#" id="bcnrdismiss" class="bdismiss">dismiss</a>
</div>
<script>
const $ = (id) => document.getElementById(id);
const T = window.theseus;
function syncHeight() { requestAnimationFrame(() => T.setChromeHeight(document.body.scrollHeight)); }
const RELOAD_SVG = `<svg viewBox="0 0 16 16"><path d="M13 8 a5 5 0 1 1 -1.5 -3.6"/><path d="M13 2.5 L13 5 L10.5 5"/></svg>`;
const STOP_SVG = `<svg viewBox="0 0 16 16"><path d="M4.5 4.5 L11.5 11.5 M11.5 4.5 L4.5 11.5"/></svg>`;
function goURL() { const v = $("url").value.trim(); if (v) T.navigate(v); }
$("url").addEventListener("keydown", (e) => { if (e.key === "Enter") goURL(); });
$("search").addEventListener("keydown", (e) => { if (e.key === "Enter") { const q = $("search").value.trim(); if (q) { T.search(q); $("search").value = ""; } } });
$("back").onclick = () => T.back();
$("fwd").onclick = () => T.forward();
$("reload").onclick = () => T.reload();
$("home").onclick = () => T.goHome();
$("logo").onclick = () => T.openSettings();
$("secbadge").onclick = () => { const r = $("secbadge").getBoundingClientRect(); T.toggleSiteInfo({ x: Math.round(r.left), y: Math.round(r.bottom + 4) }); };
// ---- search-engine picker (with an "Add / edit…" entry that opens Settings) ----
let engineCurrent = "duckduckgo";
function fillEngines(d) {
engineCurrent = d.current;
const sel = $("engine");
sel.innerHTML = d.engines.map((e) => `<option value="${e.id}">${(e.sym ? e.sym + " " : "")}${e.name.replace(/</g,"&lt;")}</option>`).join("")
+ `<option disabled>──────────</option><option value="__manage"> Add / edit engines…</option>`;
sel.value = d.current;
const cur = d.engines.find((e) => e.id === d.current);
$("search").placeholder = cur ? "Search with " + (cur.sym ? cur.sym + " " : "") + cur.name : "Search";
}
$("engine").onchange = () => {
const v = $("engine").value;
if (v === "__manage") { $("engine").value = engineCurrent; T.openSettings(); return; }
engineCurrent = v; T.setSearchEngine(v);
};
T.getSearchEngines().then(fillEngines);
T.onEngines && T.onEngines(fillEngines);
// ---- favorites bar (new-tab page only) ----
let current = { url: "", title: "" }, bookmarks = [];
function renderBookmarks() {
const box = $("bookmarks");
box.innerHTML = bookmarks.length
? bookmarks.map((b, i) => `<div class="bm" data-i="${i}" title="${(b.url||"").replace(/"/g,"&quot;")}"><span class="bt">${(b.title||b.url).replace(/</g,"&lt;")}</span><span class="bx" data-x="${i}">✕</span></div>`).join("")
: `<span class="bm-empty">No saved pages yet — click ☆ in the address bar to save one.</span>`;
box.querySelectorAll(".bm").forEach((el) => el.onclick = (e) => {
const i = Number(el.dataset.i);
if (e.target.dataset.x !== undefined) T.removeBookmark(bookmarks[i].url);
else T.navigate(bookmarks[i].url);
});
updateStar(); syncHeight();
}
function updateStar() {
const saved = !!current.url && bookmarks.some((b) => b.url === current.url);
const s = $("star"); s.textContent = saved ? "★" : "☆"; s.classList.toggle("on", saved);
s.title = saved ? "Remove from saved pages" : "Save this page";
}
$("star").onclick = () => {
if (!current.url) return;
if (bookmarks.some((b) => b.url === current.url)) T.removeBookmark(current.url);
else T.addBookmark({ title: current.title || current.url, url: current.url });
};
T.getBookmarks().then((b) => { bookmarks = b || []; renderBookmarks(); });
T.onBookmarks((b) => { bookmarks = b || []; renderBookmarks(); });
// ---- security badge + minimal registry indicator ----
function setBadge(d) {
const b = $("secbadge");
let cls = "secbadge", title = "Site information";
if (!d || d.kind === "home") title = "Theseus";
else if (d.kind === "ok") title = "Secure · " + (d.source || "BCNR") + " — click for details";
else if (d.kind === "web") title = "Connection — click for details";
else if (d.kind === "resolving") title = "Resolving…";
else if (d.kind === "nxdomain") { cls = "secbadge warn"; title = "Not registered on BCNR"; }
else if (d.kind === "error") { cls = "secbadge warn"; title = "Resolution error"; }
b.className = cls; b.title = title;
}
function setReg(d) {
const r = $("reg");
if (!d || d.kind === "home") { r.hidden = true; return; }
r.hidden = false;
if (d.kind === "web") { r.textContent = "ICANN"; r.className = "reg icann"; }
else { r.textContent = (d.registry || "BCNR") + (d.tld ? " ·." + d.tld : ""); r.className = "reg"; }
}
// ---- Tor ----
let torShown = false;
$("tor").onclick = () => { T.toggleTor(); if (!torShown) { $("tordisc").hidden = false; torShown = true; syncHeight(); } };
$("tordismiss").onclick = (e) => { e.preventDefault(); $("tordisc").hidden = true; syncHeight(); };
// ---- passive BCNR offer ----
T.onBcnrOffer((d) => {
const bar = $("bcnrbar");
if (d && d.host) { $("bcnrhost").textContent = d.host; $("bcnrreg").textContent = d.registry || "BCNR"; bar.hidden = false; }
else bar.hidden = true;
syncHeight();
});
$("bcnropen").onclick = () => { $("bcnrbar").hidden = true; syncHeight(); T.switchToBcnr(); };
$("bcnrdismiss").onclick = (e) => { e.preventDefault(); $("bcnrbar").hidden = true; syncHeight(); };
T.onTor((d) => {
const b = $("tor"); b.className = "tor " + (d.state === "on" ? "on" : d.state === "connecting" ? "connecting" : "");
b.textContent = d.state === "on" ? "🧅 Tor: On" : d.state === "connecting" ? "🧅 connecting…" : "🧅 Tor: Off";
});
// ---- tabs ----
T.onTabs((d) => {
$("back").disabled = !d.canBack; $("fwd").disabled = !d.canForward;
// loading indicator: progress bar + reload↔stop button
$("loadbar").classList.toggle("on", !!d.loading);
const rb = $("reload");
if (d.loading) { rb.innerHTML = STOP_SVG; rb.title = "Stop"; rb.onclick = () => T.stop(); }
else { rb.innerHTML = RELOAD_SVG; rb.title = "Reload"; rb.onclick = () => T.reload(); }
if (document.activeElement !== $("url")) $("url").value = d.url || "";
current.url = d.url || "";
const active = d.tabs.find((t) => t.active);
current.title = active ? active.title : "";
updateStar();
const box = $("tabs");
box.innerHTML = d.tabs.map((t) =>
`<div class="tab ${t.active ? "active" : ""}" data-id="${t.id}">${t.loading ? '<span class="spin"></span>' : ''}<span class="t">${(t.title||"New Tab").replace(/</g,"&lt;")}</span><span class="x" data-close="${t.id}">✕</span></div>`).join("") +
`<span class="newtab" id="newtab">+</span>`;
box.querySelectorAll(".tab").forEach((el) => el.onclick = (e) => {
if (e.target.dataset.close) T.closeTab(Number(e.target.dataset.close));
else T.switchTab(Number(el.dataset.id));
});
$("newtab").onclick = () => T.newTab();
});
// ---- provenance ----
T.onNav((d) => {
setBadge(d); setReg(d);
if (document.activeElement !== $("url")) {
if (d.kind === "home") $("url").value = "";
else if (d.host && !$("url").value) $("url").value = d.host;
}
// Favorites bar shows only on the new-tab / home page (hides once a page loads).
$("bookmarks").hidden = !(!d || d.kind === "home");
syncHeight();
});
syncHeight();
</script>
</body>
</html>