2026-07-29 13:54:34 +02:00
<!doctype html>
< html lang = "en" >
< head > < meta charset = "utf-8" > < title > Theseus — Settings< / title >
< style >
2026-08-02 11:39:00 +02:00
:root{ --bg:#0b0e14; --panel:#141a24; --line:rgba(255,255,255,.09);
2026-07-29 13:54:34 +02:00
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d; }
*{box-sizing:border-box}
2026-07-30 19:29:32 +02:00
body{margin:0;height:100vh;background:var(--bg);color:var(--ink);font:15px/1.6 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
.app{display:flex;height:100vh}
/* left sidebar menu */
.side{flex:none;width:220px;background:#0e131c;border-right:1px solid var(--line);padding:20px 12px;display:flex;flex-direction:column;gap:2px}
.brand{font-weight:700;color:var(--acid);font-size:15px;padding:4px 12px 16px}
.side a{display:block;padding:9px 12px;border-radius:8px;color:var(--mut);text-decoration:none;font-size:14px;cursor:pointer}
.side a:hover{background:#ffffff0a;color:var(--ink)}
.side a.active{background:rgba(214,255,61,.12);color:var(--acid)}
/* content */
.content{flex:1;overflow-y:auto;padding:2.4rem 2.4rem 4rem}
section{max-width:640px}
h1{font-size:1.4rem;margin:0 0 .2rem}
.lede{color:var(--mut);margin:0 0 1.8rem;font-size:13.5px}
2026-07-30 23:11:28 +02:00
h2.sub{font-size:11.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);margin:1.8rem 0 .2rem;border-top:1px solid var(--line);padding-top:1.4rem}
.subd{color:var(--mut);margin:0 0 1rem;font-size:13px}
2026-07-29 13:54:34 +02:00
.row{display:flex;align-items:center;gap:16px;background:var(--panel);border:1px solid var(--line);
2026-07-30 19:29:32 +02:00
border-radius:12px;padding:14px 18px;margin-bottom:10px}
2026-07-29 13:54:34 +02:00
.row .txt{flex:1}
2026-07-30 19:29:32 +02:00
.row .t{font-weight:600}
2026-07-29 13:54:34 +02:00
.row .d{color:var(--mut);font-size:13px;margin-top:2px}
2026-08-02 11:39:00 +02:00
/* control column — mode select + optional value field on the same row so the
dropdown menus don't get cut off underneath, wraps only when narrow */
.ctl{display:flex;flex-direction:row;flex-wrap:wrap;gap:6px;align-items:center;justify-content:flex-end;flex:none;max-width:60%}
2026-07-30 19:29:32 +02:00
.ctl .coords{display:flex;gap:6px}
2026-08-02 11:39:00 +02:00
select,.ctl input{background:#1b2330;color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:7px 10px;font-size:13px;outline:none;min-width:140px}
2026-07-30 19:29:32 +02:00
select:focus,.ctl input:focus{border-color:#4b7bec}
2026-08-02 11:39:00 +02:00
.ctl input{width:170px} .ctl .coords input{width:92px;min-width:auto}
/* Chromium's native < select > popup uses the page's color-scheme; when it's
"light dark" (both accepted) Chromium picks by the OS, so a dark-theme app
on a light OS shows a light popup. main.js's applyTheme() maps to
nativeTheme.themeSource, which drives prefers-color-scheme — so pinning
color-scheme via that media query keeps the popup in sync automatically. */
:root { color-scheme: dark; }
@media (prefers-color-scheme: light) { :root { color-scheme: light; } }
/* Explicit option styling — Chromium respects it in the popup on Windows. */
select option { background: #1b2330; color: var(--ink); }
@media (prefers-color-scheme: light) { select option { background: #f1f3f7; color: #1a1f28; } }
2026-07-30 20:58:45 +02:00
code{font-family:ui-monospace,monospace;font-size:12px;background:#0e131b;border:1px solid var(--line);border-radius:5px;padding:1px 5px;color:#bfeae4}
.addeng{display:flex;gap:6px} .addeng input{flex:1;background:#1b2330;color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;outline:none}
.addeng input:focus{border-color:#4b7bec}
.btn{border:1px solid #d6ff3d55;background:rgba(214,255,61,.12);color:#eaffb0;border-radius:8px;padding:8px 14px;font-size:13px;cursor:pointer}
.btn:hover{background:rgba(214,255,61,.22)}
2026-08-02 11:39:00 +02:00
/* segmented control — small toggle used elsewhere (kept for reuse) */
.segseg{display:inline-flex;background:#10151f;border:1px solid var(--line);border-radius:8px;padding:2px;gap:2px}
.segseg .seg{background:transparent;color:var(--mut);border:none;border-radius:6px;padding:6px 12px;font-size:13px;cursor:pointer;font:inherit;line-height:1.2}
.segseg .seg:hover{color:var(--ink)}
.segseg .seg.on{background:#1c2432;color:var(--ink);box-shadow:inset 0 0 0 1px var(--line)}
/* theme cards — three visual previews (System / Light / Dark) */
.themeCards{display:flex;gap:14px;margin:6px 0 4px;flex-wrap:wrap}
.themeCards .tc{background:transparent;border:2px solid var(--line);border-radius:10px;padding:10px;
display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;color:var(--ink);
font:inherit;font-size:13px;min-width:150px;transition:border-color .12s}
.themeCards .tc:hover{border-color:#4b7bec55}
.themeCards .tc.on{border-color:#4b7bec;box-shadow:0 0 0 1px #4b7bec inset}
.themeCards .mock{width:130px;height:82px;border-radius:6px;overflow:hidden;display:block;position:relative;
border:1px solid rgba(255,255,255,.08)}
.themeCards .mock .mm-chrome{position:absolute;left:0;right:0;top:0;height:22px;display:block}
.themeCards .mock .mm-chrome::before{content:"";position:absolute;left:8px;top:6px;width:8px;height:8px;border-radius:50%;background:#f6768a}
.themeCards .mock .mm-chrome::after {content:"";position:absolute;left:22px;top:6px;width:8px;height:8px;border-radius:50%;background:#f6c15c;box-shadow:14px 0 0 #6ec27d}
.themeCards .mock .mm-body{position:absolute;left:0;right:0;top:22px;bottom:0;display:block}
.themeCards .mock-light .mm-chrome{background:#e9ecf2}
.themeCards .mock-light .mm-body {background:#ffffff;background-image:linear-gradient(#0000000c 1px,transparent 1px);background-size:100% 12px;background-position:0 10px}
.themeCards .mock-dark .mm-chrome{background:#141b28}
.themeCards .mock-dark .mm-body {background:#0b0e14;background-image:linear-gradient(#ffffff10 1px,transparent 1px);background-size:100% 12px;background-position:0 10px}
.themeCards .mock-system .mm-chrome{background:linear-gradient(90deg,#141b28 0 50%,#e9ecf2 50% 100%)}
.themeCards .mock-system .mm-body{background:linear-gradient(90deg,#0b0e14 0 50%,#ffffff 50% 100%)}
.themeCards .tc-label{font-weight:500;color:var(--ink)}
.polrow{display:flex;align-items:center;gap:10px;background:#10151f;border:1px solid var(--line);border-radius:8px;padding:8px 12px;font-size:13px;cursor:pointer}
.polrow:hover{background:#141c28}
.polrow input{accent-color:#d6ff3d}
.pmuted{color:var(--mut)}
2026-07-30 20:58:45 +02:00
.ceng{display:flex;align-items:center;gap:8px;background:#10151f;border:1px solid var(--line);border-radius:8px;padding:6px 10px;margin-bottom:6px;font-size:13px}
2026-07-30 22:09:26 +02:00
.ceng .cs{font-size:14px;flex:none}
2026-07-30 22:55:52 +02:00
/* engine checklist */
.eng{display:flex;align-items:center;gap:10px;background:#10151f;border:1px solid var(--line);border-radius:8px;padding:6px 10px;margin-bottom:6px;font-size:13px}
.eng .eic{width:18px;height:18px;flex:none;display:grid;place-items:center}
.eng .eic .ei{width:16px;height:16px;border-radius:3px} .eng .eic .es{font-size:14px}
.eng .enm{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.eng .cx{cursor:pointer;color:var(--dim);border:none;background:transparent;font-size:13px} .eng .cx:hover{color:#f6768a}
2026-07-31 21:53:01 +02:00
.eng{cursor:default}
.eng .grip{flex:none;color:var(--dim);cursor:grab;font-size:14px;line-height:1;padding:0 2px;user-select:none}
.eng .grip:active{cursor:grabbing}
.eng.dragging{opacity:.45}
.eng.over{border-color:var(--acid);box-shadow:0 -2px 0 var(--acid) inset}
2026-08-02 11:39:00 +02:00
.ehdr{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);margin:14px 0 6px;padding-top:2px}
.ehdr:first-child{margin-top:0}
/* engine catalog panel — appears under the enabled list when "+ Add" is clicked */
.engcat{margin-top:6px;padding:12px 12px 10px;background:#0e131c;border:1px solid var(--line);border-radius:10px}
.engcat .cat{display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:6px;font-size:13px}
.engcat .cat:hover{background:#141b26}
.engcat .cat .eic{width:18px;height:18px;flex:none;display:grid;place-items:center}
.engcat .cat .eic .ei{width:16px;height:16px;border-radius:3px} .engcat .cat .eic .es{font-size:14px}
.engcat .cat .enm{flex:1}
.engcat .cat .kind{font-size:10.5px;letter-spacing:.05em;color:var(--dim);padding:1px 6px;border:1px solid var(--line);border-radius:999px}
.engcat .cat .add{background:transparent;border:1px solid var(--line);color:var(--ink);border-radius:6px;padding:4px 10px;font-size:12px;cursor:pointer}
.engcat .cat .add:hover{background:rgba(214,255,61,.14);border-color:#d6ff3d55;color:#eaffb0}
.engcat .cempty2{color:var(--dim);font-size:12.5px;padding:6px 8px}
2026-07-30 22:55:52 +02:00
.sw.sm{width:38px;height:22px} .sw.sm .knob{width:15px;height:15px} .sw.sm input:checked + .track .knob{transform:translateX(16px)}
2026-07-30 20:58:45 +02:00
.ceng .cn{font-weight:600} .ceng .cu{color:var(--dim);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
.ceng .cx{cursor:pointer;color:#8b98a9;border:none;background:transparent;font-size:13px} .ceng .cx:hover{color:#f6768a}
.cempty{color:var(--dim);font-size:12.5px}
2026-07-29 13:54:34 +02:00
/* toggle */
.sw{position:relative;width:46px;height:26px;flex:none;cursor:pointer}
.sw input{opacity:0;width:0;height:0}
.track{position:absolute;inset:0;background:#2b3444;border:1px solid var(--line);border-radius:999px;transition:.15s}
.knob{position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:#8b98a9;transition:.15s}
.sw input:checked + .track{background:rgba(214,255,61,.25);border-color:#d6ff3d55}
.sw input:checked + .track .knob{transform:translateX(20px);background:var(--acid)}
.note{color:var(--dim);font-size:12.5px;margin-top:1.4rem;border-top:1px solid var(--line);padding-top:1rem}
2026-07-30 22:00:01 +02:00
/* light theme (placed last so these win over the dark base rules) */
@media (prefers-color-scheme: light){
:root{ --bg:#eef0f4; --panel:#ffffff; --line:rgba(0,0,0,.11); --ink:#1a1f28; --mut:#4a5262; --dim:#7b8494; }
.side{ background:#f6f8fb; }
.side a:hover{ background:rgba(0,0,0,.05); }
select, .ctl input, .addeng input{ background:#f1f3f7; }
.track{ background:#c8cfdb; }
.knob{ background:#8a93a2; }
2026-07-30 22:55:52 +02:00
code, .ceng, .eng{ background:#eef1f6; }
2026-07-30 22:00:01 +02:00
}
2026-07-29 13:54:34 +02:00
< / style > < / head >
< body >
2026-07-30 19:29:32 +02:00
< div class = "app" >
< nav class = "side" >
< div class = "brand" > ⛓ Theseus< / div >
< a data-sec = "general" class = "active" > General< / a >
2026-08-02 11:39:00 +02:00
< a data-sec = "search" > Search< / a >
< a data-sec = "naming" > Naming< / a >
2026-07-30 19:29:32 +02:00
< a data-sec = "performance" > Performance< / a >
< a data-sec = "privacy" > Privacy< / a >
< / nav >
< div class = "content" >
<!-- GENERAL -->
< section id = "general" >
< h1 > General< / h1 >
< p class = "lede" > Changes apply immediately and are saved for next time.< / p >
2026-08-02 11:39:00 +02:00
< h2 class = "sub" style = "border-top:0;padding-top:0;margin-top:0" > Startup< / h2 >
2026-07-30 22:00:01 +02:00
< div class = "row" >
2026-08-02 11:39:00 +02:00
< div class = "txt" > < div class = "t" > Open previous windows and tabs< / div > < div class = "d" > Restore the tabs from your last session when Theseus starts.< / div > < / div >
< label class = "sw" > < input type = "checkbox" id = "restoreSession" > < span class = "track" > < span class = "knob" > < / span > < / span > < / label >
< / div >
< h2 class = "sub" > Appearance< / h2 >
< p class = "subd" > Choose a theme for the browser. < b > System< / b > follows your operating system's light/dark setting.< / p >
< div class = "themeCards" id = "theme" role = "radiogroup" aria-label = "Theme" >
< button type = "button" class = "tc" data-val = "system" role = "radio" aria-checked = "false" >
< span class = "mock mock-system" > < span class = "mm-chrome" > < / span > < span class = "mm-body" > < / span > < / span >
< span class = "tc-label" > System< / span >
< / button >
< button type = "button" class = "tc" data-val = "light" role = "radio" aria-checked = "false" >
< span class = "mock mock-light" > < span class = "mm-chrome" > < / span > < span class = "mm-body" > < / span > < / span >
< span class = "tc-label" > Light< / span >
< / button >
< button type = "button" class = "tc" data-val = "dark" role = "radio" aria-checked = "false" >
< span class = "mock mock-dark" > < span class = "mm-chrome" > < / span > < span class = "mm-body" > < / span > < / span >
< span class = "tc-label" > Dark< / span >
< / button >
2026-07-30 22:00:01 +02:00
< / div >
2026-08-02 11:39:00 +02:00
< / section >
<!-- SEARCH -->
< section id = "search" hidden >
< h1 > Search< / h1 >
< p class = "lede" > Pick what your address bar and the toolbar dropdown search with.< / p >
2026-07-30 19:29:32 +02:00
< div class = "row" >
2026-08-02 11:39:00 +02:00
< div class = "txt" > < div class = "t" > Default search engine< / div > < div class = "d" > Used when you type into the address bar.< / div > < / div >
2026-07-30 19:29:32 +02:00
< div class = "ctl" > < select id = "searchEngine" > < / select > < / div >
2026-07-29 13:54:34 +02:00
< / div >
2026-07-30 22:55:52 +02:00
< div class = "row" style = "flex-direction:column;align-items:stretch;gap:10px" >
2026-08-02 11:39:00 +02:00
< div class = "txt" style = "display:flex;align-items:center;justify-content:space-between;gap:12px" >
< div >
< div class = "t" > Additional search engines< / div >
< div class = "d" > These appear in the toolbar dropdown. Drag to reorder. Toggle off to move an engine back to the catalog.< / div >
< / div >
< button id = "engAddBtn" class = "btn" type = "button" > + Add search engine< / button >
< / div >
2026-07-30 22:55:52 +02:00
< div id = "engineList" > < / div >
2026-08-02 11:39:00 +02:00
<!-- Catalog: hidden until "Add" is clicked. Shows built - in engines the user
hasn't enabled + a custom-URL form. -->
< div id = "engineCatalog" class = "engcat" hidden >
< div class = "ehdr" > Add from catalog< / div >
< div id = "catalogList" > < / div >
< div class = "ehdr" > Or add a custom URL< / div >
< div class = "addeng" >
< input id = "engSym" placeholder = "🔍" style = "max-width:52px;text-align:center;flex:none" >
< input id = "engName" placeholder = "Name (e.g. SearXNG)" >
< input id = "engUrl" placeholder = "https://example.com/search?q=%s" >
< button id = "engAdd" class = "btn" > Add< / button >
< / div >
2026-07-30 20:58:45 +02:00
< / div >
< / div >
2026-08-02 11:39:00 +02:00
< / section >
<!-- NAMING -->
< section id = "naming" hidden >
< h1 > Naming< / h1 >
< p class = "lede" > How Theseus picks between the < b > Bitcoin Cash Name Registry (BCNR)< / b > and < b > ICANN< / b > when a name exists in both.< / p >
< div class = "row" style = "flex-direction:column;align-items:stretch;gap:10px" >
< div class = "txt" > < div class = "t" > Collision policy< / div >
< div class = "d" > A name only exists in both registries when its TLD isn't BCNR-native (e.g. < code > .de< / code > ). BCNR-native TLDs (< code > .bch< / code > , < code > .p2p< / code > , …) never conflict.< / div > < / div >
< div id = "policyList" style = "display:flex;flex-direction:column;gap:6px" >
< label class = "polrow" > < input type = "radio" name = "collisionPolicy" value = "bcnr-first" > < span > < b > BCNR first< / b > < span class = "pmuted" > — BCNR wins conflicts; falls back to ICANN.< / span > < / span > < / label >
< label class = "polrow" > < input type = "radio" name = "collisionPolicy" value = "icann-first" > < span > < b > ICANN first< / b > < span class = "pmuted" > — ICANN wins conflicts; BCNR fills gaps.< / span > < / span > < / label >
< label class = "polrow" > < input type = "radio" name = "collisionPolicy" value = "soft" > < span > < b > Ask each time< / b > < span class = "pmuted" > — an "Open with…" prompt on conflict, remembered per name.< / span > < / span > < / label >
< / div >
< / div >
< div class = "row" style = "flex-direction:column;align-items:stretch;gap:8px" >
< div class = "txt" > < div class = "t" > Remembered choices< / div >
< div class = "d" > "Always use …" picks you made from the switcher or the prompt. Reset them to be asked again.< / div > < / div >
< div id = "colSummary" class = "pmuted" style = "font-size:12.5px" > < / div >
< div > < button id = "resetCollisions" class = "btn" > Reset remembered choices< / button > < / div >
2026-07-29 13:54:34 +02:00
< / div >
2026-07-30 19:29:32 +02:00
< / section >
<!-- PERFORMANCE -->
< section id = "performance" hidden >
< h1 > Performance< / h1 >
< p class = "lede" > Keep Theseus light on resources.< / p >
< div class = "row" >
< div class = "txt" > < div class = "t" > Throttle inactive tabs< / div > < div class = "d" > Background and inactive tabs use far less CPU. Recommended.< / div > < / div >
< label class = "sw" > < input type = "checkbox" id = "backgroundThrottle" > < span class = "track" > < span class = "knob" > < / span > < / span > < / label >
< / div >
< / section >
<!-- PRIVACY -->
< section id = "privacy" hidden >
< h1 > Privacy< / h1 >
< p class = "lede" > Reduce tracking and lock down device access.< / p >
< div class = "row" >
2026-07-30 22:00:01 +02:00
< div class = "txt" > < div class = "t" > WebRTC IP policy< / div >
< div class = "d" > Controls which IP addresses WebRTC may reveal — the same thing the "WebRTC Network Limiter" extension does, built in. < b > Public interface only< / b > hides your local IP; < b > Disable non-proxied UDP< / b > is strongest. Tor forces the strongest automatically.< / div > < / div >
< div class = "ctl" > < select id = "webrtcMode" >
< option value = "default" > Allow all (default)< / option >
< option value = "public_only" > Public interface only< / option >
< option value = "public_private" > Public + private interfaces< / option >
< option value = "disable_udp" > Disable non-proxied UDP< / option >
< / select > < / div >
2026-07-30 19:29:32 +02:00
< / div >
< div class = "row" >
< div class = "txt" > < div class = "t" > Block camera< / div > < div class = "d" > Deny camera by default — also hides its name from fingerprinting.< / div > < / div >
< label class = "sw" > < input type = "checkbox" id = "blockCamera" > < span class = "track" > < span class = "knob" > < / span > < / span > < / label >
< / div >
< div class = "row" >
< div class = "txt" > < div class = "t" > Block microphone< / div > < div class = "d" > Deny microphone by default — also hides its name from fingerprinting.< / div > < / div >
< label class = "sw" > < input type = "checkbox" id = "blockMicrophone" > < span class = "track" > < span class = "knob" > < / span > < / span > < / label >
< / div >
2026-07-30 23:11:28 +02:00
< div class = "row" >
< div class = "txt" > < div class = "t" > Hide media devices< / div > < div class = "d" > Blank the labels and IDs of all cameras, microphones < b > and speakers< / b > from < code > enumerateDevices()< / code > — closes a WebRTC fingerprinting leak, like Firefox.< / div > < / div >
< label class = "sw" > < input type = "checkbox" id = "hideMediaDevices" > < span class = "track" > < span class = "knob" > < / span > < / span > < / label >
< / div >
< h2 class = "sub" > Anti-fingerprinting< / h2 >
< p class = "subd" > For each: < b > Show< / b > (real), < b > Hide< / b > (neutral value), < b > Spoof< / b > (a decoy), or < b > Manual< / b > (set your own).< / p >
2026-07-30 19:29:32 +02:00
< div class = "row" >
< div class = "txt" > < div class = "t" > Timezone< / div > < div class = "d" > What sites read via JavaScript (Intl / Date).< / div > < / div >
< div class = "ctl" >
< select id = "timezoneMode" > < option value = "show" > Show real< / option > < option value = "hide" > Hide (UTC)< / option > < option value = "spoof" > Spoof (auto)< / option > < option value = "manual" > Manual…< / option > < / select >
2026-07-30 23:26:00 +02:00
< input id = "timezoneValue" list = "tzList" placeholder = "pick or type e.g. Europe/Berlin" hidden >
< datalist id = "tzList" >
< option value = "UTC" > < option value = "Europe/London" > < option value = "Europe/Berlin" > < option value = "Europe/Paris" > < option value = "Europe/Moscow" >
< option value = "America/New_York" > < option value = "America/Chicago" > < option value = "America/Los_Angeles" > < option value = "America/Sao_Paulo" >
< option value = "Asia/Tokyo" > < option value = "Asia/Shanghai" > < option value = "Asia/Kolkata" > < option value = "Asia/Dubai" > < option value = "Australia/Sydney" > < option value = "Africa/Nairobi" >
< / datalist >
2026-07-30 19:29:32 +02:00
< / div >
< / div >
< div class = "row" >
2026-07-30 23:11:28 +02:00
< div class = "txt" > < div class = "t" > Language< / div > < div class = "d" > navigator.language and the Accept-Language header. < b > Spoof< / b > reports a major world language; < b > Manual< / b > lets you type any locale.< / div > < / div >
2026-07-30 19:29:32 +02:00
< div class = "ctl" >
2026-07-30 23:11:28 +02:00
< select id = "languageMode" > < option value = "show" > Show real< / option > < option value = "hide" > Hide (en-US)< / option > < option value = "spoof" > Spoof (choose)< / option > < option value = "manual" > Manual…< / option > < / select >
< select id = "languageSpoof" hidden >
< option value = "en-US" > English< / option >
< option value = "zh-CN" > Chinese (中文)< / option >
< option value = "es-ES" > Spanish (Español)< / option >
< option value = "hi-IN" > Hindi (हिन्दी)< / option >
< option value = "ar" > Arabic (العربية)< / option >
< option value = "pt-BR" > Portuguese (Português)< / option >
< option value = "ru-RU" > Russian (Русский)< / option >
< option value = "fr-FR" > French (Français)< / option >
< option value = "de-DE" > German (Deutsch)< / option >
< option value = "ja-JP" > Japanese (日本語)< / option >
< / select >
2026-07-30 23:26:00 +02:00
< input id = "languageValue" list = "langList" placeholder = "pick or type e.g. it-IT" hidden >
< datalist id = "langList" >
< option value = "en-US" > < option value = "en-GB" > < option value = "zh-CN" > < option value = "es-ES" > < option value = "hi-IN" > < option value = "ar" >
< option value = "pt-BR" > < option value = "ru-RU" > < option value = "fr-FR" > < option value = "de-DE" > < option value = "ja-JP" > < option value = "it-IT" > < option value = "ko-KR" > < option value = "nl-NL" > < option value = "tr-TR" >
< / datalist >
2026-07-30 19:29:32 +02:00
< / div >
< / div >
< div class = "row" >
2026-07-30 23:11:28 +02:00
< div class = "txt" > < div class = "t" > Location< / div > < div class = "d" > < b > Hide< / b > denies geolocation; < b > Spoof< / b > reports a chosen region; < b > Manual< / b > reports exact coordinates.< / div > < / div >
2026-07-30 19:29:32 +02:00
< div class = "ctl" >
2026-07-30 23:11:28 +02:00
< select id = "locationMode" > < option value = "show" > Show real< / option > < option value = "hide" > Hide (block)< / option > < option value = "spoof" > Spoof (region)< / option > < option value = "manual" > Manual…< / option > < / select >
< select id = "locationRegion" hidden >
< option value = "europe" > Europe< / option >
< option value = "asia" > Asia< / option >
< option value = "north_america" > North America< / option >
< option value = "south_america" > South America< / option >
< option value = "africa" > Africa< / option >
< option value = "middle_east" > Middle East< / option >
< option value = "australia" > Australia< / option >
< / select >
2026-07-30 19:29:32 +02:00
< div class = "coords" id = "locationCoords" hidden >
2026-07-30 23:26:00 +02:00
< input id = "locationCity" list = "cityList" placeholder = "pick a city…" style = "width:112px" >
2026-07-30 19:29:32 +02:00
< input id = "locationLat" placeholder = "lat" > < input id = "locationLon" placeholder = "lon" >
< / div >
2026-07-30 23:26:00 +02:00
< datalist id = "cityList" >
< option value = "London" > < option value = "Berlin" > < option value = "Paris" > < option value = "Moscow" > < option value = "New York" > < option value = "Los Angeles" >
< option value = "São Paulo" > < option value = "Tokyo" > < option value = "Shanghai" > < option value = "Mumbai" > < option value = "Dubai" > < option value = "Sydney" > < option value = "Nairobi" > < option value = "Singapore" >
< / datalist >
2026-07-30 19:29:32 +02:00
< / div >
< / div >
< div class = "note" > These reduce tracking and hide your IP, but a custom browser can still be fingerprinted. For maximum anonymity, use the Tor Browser.< / div >
< / section >
2026-07-29 13:54:34 +02:00
< / div >
< / div >
< script >
const C = window.cfg;
2026-07-30 19:29:32 +02:00
// sidebar navigation
2026-08-02 11:39:00 +02:00
const sections = ["general", "search", "naming", "performance", "privacy"];
2026-07-30 19:29:32 +02:00
document.querySelectorAll(".side a").forEach((a) => a.onclick = () => {
document.querySelectorAll(".side a").forEach((x) => x.classList.toggle("active", x === a));
for (const s of sections) document.getElementById(s).hidden = (s !== a.dataset.sec);
});
2026-07-30 23:11:28 +02:00
const TOGGLES = ["restoreSession", "backgroundThrottle", "blockCamera", "blockMicrophone", "hideMediaDevices"];
2026-07-29 13:54:34 +02:00
C.get().then((s) => {
2026-07-30 19:29:32 +02:00
for (const k of TOGGLES) {
const el = document.getElementById(k); if (!el) continue;
2026-07-29 13:54:34 +02:00
el.checked = !!s[k];
el.addEventListener("change", () => C.set(k, el.checked));
}
2026-07-30 20:58:45 +02:00
// search engines: default picker + custom-engine list + add form
const sel = document.getElementById("searchEngine");
const esc = (s) => String(s || "").replace(/< /g, "< ");
2026-07-30 22:55:52 +02:00
const engIcon = (e) => e.favicon
? `< img class = "ei" src = "${esc(e.favicon)}" onerror = "this.replaceWith(Object.assign(document.createElement('span'),{className:'es',textContent:'${e.sym || " 🔍 " } ' } ) ) " > `
: `< span class = "es" > ${e.sym || "🔍"}< / span > `;
2026-08-02 11:39:00 +02:00
const ENGINE_KINDS = [
{ key: "search", label: "Search engines" },
{ key: "llm", label: "AI answer engines" },
];
2026-07-30 20:58:45 +02:00
function renderEngines(d) {
2026-07-30 22:55:52 +02:00
// default engine: pick from the ENABLED engines (those shown in the dropdown)
const enabled = d.engines.filter((e) => e.enabled);
2026-08-02 11:39:00 +02:00
// group the dropdown too, so search and LLM don't intermix
sel.innerHTML = ENGINE_KINDS.map(({ key, label }) => {
const opts = enabled.filter((e) => (e.kind || "search") === key)
.map((e) => `< option value = "${e.id}" > ${(e.sym ? e.sym + " " : "")}${esc(e.name)}< / option > `).join("");
return opts ? `< optgroup label = "${label}" > ${opts}< / optgroup > ` : "";
}).join("");
2026-07-30 19:29:32 +02:00
sel.value = d.current;
2026-08-02 11:39:00 +02:00
// Main list = the engines the user has enabled (i.e. actually shown in the
// toolbar dropdown). Everything else lives in the catalog panel below and
// is added via "+ Add". Drag reorders within a kind; toggling off moves an
// engine back to the catalog; custom engines get a ✕ to delete entirely.
2026-07-30 22:55:52 +02:00
const list = document.getElementById("engineList");
2026-08-02 11:39:00 +02:00
const rowFor = (e) => `< div class = "eng" data-id = "${e.id}" data-kind = "${e.kind || " search " } " draggable = "true" > ` +
2026-07-31 21:53:01 +02:00
`< span class = "grip" title = "Drag to reorder" > ⠿< / span > ` +
2026-07-30 23:26:00 +02:00
`< span class = "eic" > ${engIcon(e)}< / span > < span class = "enm" > ${esc(e.name)}< / span > ` +
2026-07-30 22:55:52 +02:00
(e.builtin
? `< label class = "sw sm" > < input type = "checkbox" data-id = "${e.id}" $ { e . enabled ? " checked " : " " } > < span class = "track" > < span class = "knob" > < / span > < / span > < / label > `
2026-08-02 11:39:00 +02:00
: `< button class = "cx" data-id = "${e.id}" title = "Remove" > ✕< / button > `) + `< / div > `;
list.innerHTML = ENGINE_KINDS.map(({ key, label }) => {
const rows = enabled.filter((e) => (e.kind || "search") === key).map(rowFor).join("");
if (!rows) return "";
return `< div class = "ehdr" > ${label}< / div > ${rows}`;
}).join("");
// Catalog = built-in engines the user has NOT enabled, ready to add.
const cat = document.getElementById("catalogList");
if (cat) {
const off = d.engines.filter((e) => e.builtin & & !e.enabled);
const catRow = (e) => `< div class = "cat" data-id = "${e.id}" > ` +
`< span class = "eic" > ${engIcon(e)}< / span > ` +
`< span class = "enm" > ${esc(e.name)}< / span > ` +
`< span class = "kind" > ${(e.kind || "search") === "llm" ? "AI" : "Search"}< / span > ` +
`< button class = "add" data-add = "${e.id}" > + Add< / button > < / div > `;
cat.innerHTML = off.length
? off.map(catRow).join("")
: `< div class = "cempty2" > All built-in engines are enabled. Add a custom URL below.< / div > `;
cat.querySelectorAll(".add").forEach((b) => b.onclick = () => C.setEngineEnabled(b.dataset.add, true).then(renderEngines));
}
2026-07-30 22:55:52 +02:00
list.querySelectorAll('input[type="checkbox"]').forEach((cb) => cb.onchange = () => C.setEngineEnabled(cb.dataset.id, cb.checked).then(renderEngines));
2026-07-30 20:58:45 +02:00
list.querySelectorAll(".cx").forEach((b) => b.onclick = () => C.removeEngine(b.dataset.id).then(renderEngines));
2026-08-02 11:39:00 +02:00
// drag-and-drop reorder — same-kind only (dropping a Search engine into
// the LLM section would just re-group visually on next render, so we
// reject cross-kind drags outright).
2026-07-31 21:53:01 +02:00
let dragId = null;
2026-08-02 11:39:00 +02:00
let dragKind = null;
const sameKind = (row) => row.dataset.kind === dragKind;
2026-07-31 21:53:01 +02:00
list.querySelectorAll(".eng").forEach((row) => {
2026-08-02 11:39:00 +02:00
row.addEventListener("dragstart", (e) => { dragId = row.dataset.id; dragKind = row.dataset.kind; e.dataTransfer.effectAllowed = "move"; row.classList.add("dragging"); });
2026-07-31 21:53:01 +02:00
row.addEventListener("dragend", () => { row.classList.remove("dragging"); list.querySelectorAll(".eng").forEach((r) => r.classList.remove("over")); });
2026-08-02 11:39:00 +02:00
row.addEventListener("dragover", (e) => {
if (!sameKind(row)) { e.dataTransfer.dropEffect = "none"; return; }
e.preventDefault(); e.dataTransfer.dropEffect = "move";
if (row.dataset.id !== dragId) row.classList.add("over");
});
2026-07-31 21:53:01 +02:00
row.addEventListener("dragleave", () => row.classList.remove("over"));
row.addEventListener("drop", (e) => {
e.preventDefault(); row.classList.remove("over");
2026-08-02 11:39:00 +02:00
if (!dragId || dragId === row.dataset.id || !sameKind(row)) return;
2026-07-31 21:53:01 +02:00
const ids = [...list.querySelectorAll(".eng")].map((el) => el.dataset.id);
const from = ids.indexOf(dragId), to = ids.indexOf(row.dataset.id);
ids.splice(from, 1); ids.splice(to, 0, dragId);
C.setEngineOrder(ids).then(renderEngines);
});
});
2026-07-30 20:58:45 +02:00
}
sel.onchange = () => C.set("searchEngine", sel.value);
2026-08-02 11:39:00 +02:00
// appearance (theme) — three visual cards: system | light | dark. Any
// unrecognised saved value falls back to "system" (follow the OS).
2026-07-30 22:00:01 +02:00
const th = document.getElementById("theme");
2026-08-02 11:39:00 +02:00
let themeValue = ["system", "light", "dark"].includes(s.theme) ? s.theme : "system";
const paintTheme = () => th.querySelectorAll(".tc").forEach((b) => {
const on = b.dataset.val === themeValue;
b.classList.toggle("on", on);
b.setAttribute("aria-checked", on ? "true" : "false");
});
paintTheme();
th.querySelectorAll(".tc").forEach((b) => b.onclick = () => {
themeValue = b.dataset.val; paintTheme(); C.set("theme", themeValue);
});
2026-07-30 22:00:01 +02:00
// WebRTC IP policy
const wm = document.getElementById("webrtcMode");
wm.value = s.webrtcMode || "public_only";
wm.onchange = () => C.set("webrtcMode", wm.value);
2026-07-30 20:58:45 +02:00
document.getElementById("engAdd").onclick = () => {
const name = document.getElementById("engName").value.trim();
const url = document.getElementById("engUrl").value.trim();
2026-07-30 22:09:26 +02:00
const sym = document.getElementById("engSym").value.trim();
2026-07-30 20:58:45 +02:00
if (!name || !url.includes("%s")) { alert("Enter a name and a URL containing %s (where the query goes)."); return; }
2026-07-30 22:09:26 +02:00
C.addEngine({ name, url, sym }).then((d) => {
document.getElementById("engName").value = ""; document.getElementById("engUrl").value = ""; document.getElementById("engSym").value = "";
renderEngines(d);
});
2026-07-30 20:58:45 +02:00
};
2026-08-02 11:39:00 +02:00
// "+ Add search engine" toggles the catalog panel below the enabled list.
const catBtn = document.getElementById("engAddBtn");
const catBox = document.getElementById("engineCatalog");
if (catBtn & & catBox) {
catBtn.onclick = () => {
const open = catBox.hidden;
catBox.hidden = !open;
catBtn.textContent = open ? "− Hide catalog" : "+ Add search engine";
};
}
2026-07-30 20:58:45 +02:00
C.engines().then(renderEngines);
2026-07-30 19:29:32 +02:00
// anti-fingerprinting mode selectors, with value field(s) shown on "manual"
const bind = (mode, showValIf, apply) => {
const m = document.getElementById(mode);
2026-07-29 13:54:34 +02:00
m.value = s[mode] || "show";
2026-07-30 19:29:32 +02:00
apply(m.value === "manual");
m.addEventListener("change", () => { C.set(mode, m.value); apply(m.value === "manual"); });
};
const val = (id) => { const v = document.getElementById(id); v.value = s[id] ?? ""; v.addEventListener("change", () => C.set(id, v.value.trim())); return v; };
2026-07-30 23:11:28 +02:00
const tzV = val("timezoneValue"), lgV = val("languageValue");
val("locationLat"); val("locationLon");
2026-07-30 19:29:32 +02:00
bind("timezoneMode", null, (manual) => tzV.hidden = !manual);
2026-07-30 23:11:28 +02:00
// language: top-10 dropdown when spoofing, free-text locale when manual
const lngMode = document.getElementById("languageMode");
const lngSpoof = document.getElementById("languageSpoof");
lngSpoof.value = s.languageSpoof || "en-US";
lngSpoof.addEventListener("change", () => C.set("languageSpoof", lngSpoof.value));
const applyLng = () => { lngSpoof.hidden = lngMode.value !== "spoof"; lgV.hidden = lngMode.value !== "manual"; };
lngMode.value = s.languageMode || "show";
applyLng();
lngMode.addEventListener("change", () => { C.set("languageMode", lngMode.value); applyLng(); });
// location: region dropdown when spoofing, exact coords when manual
const locMode = document.getElementById("locationMode");
const locRegion = document.getElementById("locationRegion");
const locCoords = document.getElementById("locationCoords");
locRegion.value = s.locationRegion || "europe";
locRegion.addEventListener("change", () => C.set("locationRegion", locRegion.value));
const applyLoc = () => { locRegion.hidden = locMode.value !== "spoof"; locCoords.hidden = locMode.value !== "manual"; };
locMode.value = s.locationMode || "show";
applyLoc();
locMode.addEventListener("change", () => { C.set("locationMode", locMode.value); applyLoc(); });
2026-07-30 23:26:00 +02:00
// picking a city fills the exact lat/lon fields
const CITY = { "London":[51.5074,-0.1278],"Berlin":[52.52,13.405],"Paris":[48.8566,2.3522],"Moscow":[55.7558,37.6173],"New York":[40.7128,-74.006],"Los Angeles":[34.0522,-118.2437],"São Paulo":[-23.5505,-46.6333],"Tokyo":[35.6762,139.6503],"Shanghai":[31.2304,121.4737],"Mumbai":[19.076,72.8777],"Dubai":[25.2048,55.2708],"Sydney":[-33.8688,151.2093],"Nairobi":[-1.2921,36.8219],"Singapore":[1.3521,103.8198] };
document.getElementById("locationCity").addEventListener("change", (e) => {
const c = CITY[e.target.value.trim()]; if (!c) return;
const la = document.getElementById("locationLat"), lo = document.getElementById("locationLon");
la.value = c[0]; lo.value = c[1]; C.set("locationLat", String(c[0])); C.set("locationLon", String(c[1]));
});
2026-08-02 11:39:00 +02:00
// ---- Naming section: BCNR/ICANN collision policy + remembered choices ----
function refreshCollisions() {
C.collisionState().then((cs) => {
// pick the current radio
document.querySelectorAll('input[name="collisionPolicy"]').forEach((r) => { r.checked = (r.value === cs.policy); });
const nn = Object.keys(cs.byName || {}).length, tn = Object.keys(cs.byTld || {}).length;
const bc = (cs.bcnrTlds || []).length;
document.getElementById("colSummary").textContent =
`Remembered: ${nn} name${nn === 1 ? "" : "s"}, ${tn} TLD${tn === 1 ? "" : "s"}. `
+ `BCNR-native TLDs on chain: ${bc}.`;
}).catch(() => {});
}
refreshCollisions();
document.querySelectorAll('input[name="collisionPolicy"]').forEach((r) => {
r.addEventListener("change", () => { if (r.checked) C.setCollisionPolicy(r.value).then(refreshCollisions); });
});
document.getElementById("resetCollisions").onclick = () => {
C.resetCollisions().then(refreshCollisions);
};
2026-07-29 13:54:34 +02:00
});
< / script >
< / body >
< / html >