theseus/bundled-addons/blocker/panel.html

126 lines
8.2 KiB
HTML
Raw Normal View History

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Shield</title>
<style>
:root { color-scheme: light dark;
--bg:#0e131c; --panel:#141a24; --panel2:#191f2b; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#0ac18e; --blue:#4c9eff; --warn:#f5a524; --danger:#ff5b5b; }
@media (prefers-color-scheme: light) {
:root { --bg:#f8faff; --panel:#ffffff; --panel2:#eff3fb; --line:rgba(0,0,0,.10);
--ink:#1a1f2b; --mut:#5c6577; --dim:#8a93a5; --blue:#2b6fd6; }
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body { background: var(--bg); color: var(--ink); font: 13px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; display: flex; flex-direction: column; }
header { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--line); background: var(--panel); }
header h1 { margin: 0; font-size: 14px; font-weight: 600; }
header .pill { margin-left: auto; }
.pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 9px; border-radius: 999px;
font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
background: rgba(139,152,169,.14); color: var(--mut); }
.pill.on { color: #0b0e14; background: var(--acid); }
.pill.off { color: #fff; background: var(--danger); }
.pill .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.body { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 12px; }
.card { border: 1px solid var(--line); background: var(--panel); border-radius: 14px; padding: 16px; }
.site { display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center; }
.site .host { font: 600 13px/1.3 ui-monospace, "Cascadia Code", Consolas, monospace; color: var(--mut); overflow-wrap: anywhere; }
.site .num { font-size: 40px; font-weight: 700; line-height: 1.1; color: var(--blue); font-variant-numeric: tabular-nums; }
.site .num.allowed { color: var(--dim); }
.site .lbl { color: var(--mut); }
.site .sub { color: var(--dim); font-size: 11.5px; }
.btn { border: 1px solid var(--line); background: var(--panel2); color: var(--ink); padding: 9px 16px; border-radius: 10px;
font: inherit; font-weight: 600; cursor: pointer; }
.btn:hover { border-color: rgb(from var(--blue) r g b / .5); }
.btn.primary { background: var(--blue); color: #fff; border-color: transparent; }
.btn.quiet { background: transparent; }
.btn:disabled { opacity: .5; cursor: default; }
.btn.wide { margin-top: 10px; min-width: 200px; }
.row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.row + .row { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line); }
.row .k { font-weight: 600; }
.row .d { color: var(--dim); font-size: 11.5px; }
.row .v { color: var(--mut); font-variant-numeric: tabular-nums; text-align: right; }
.switch { position: relative; width: 40px; height: 22px; border-radius: 11px; background: var(--dim); border: none; cursor: pointer; flex: none; transition: background .15s; }
.switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%; background: #fff; transition: left .15s; }
.switch.on { background: var(--acid); } .switch.on::after { left: 20px; }
.note { color: var(--dim); font-size: 11.5px; line-height: 1.45; }
.note.warn { color: var(--warn); }
h2 { margin: 0 0 8px; font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--dim); }
</style>
</head>
<body>
<header><span>🛡</span><h1>Shield</h1><span class="pill" id="pill"><span class="dot"></span><span id="pillText">…</span></span></header>
<div class="body">
<div class="card site" id="siteCard">
<div class="lbl" id="siteLbl">Open a site to see what Shield stops there.</div>
</div>
<div class="card">
<div class="row">
<div><div class="k">Block trackers and ads</div><div class="d">On every site, using EasyList and EasyPrivacy.</div></div>
<button class="switch" id="enabled" role="switch" aria-checked="true" title="Turn Shield on or off"></button>
</div>
<div class="row">
<div><div class="k">Blocked since install</div><div class="d">Requests that never left the browser.</div></div>
<div class="v" id="total">0</div>
</div>
</div>
<div class="card">
<h2>Rules</h2>
<div id="lists"></div>
<div class="row" style="margin-top:12px; padding-top:12px; border-top:1px solid var(--line)">
<div class="note" id="updated"></div>
<button class="btn quiet" id="refresh">Update now</button>
</div>
</div>
<div class="note"><a href="#" id="toSettings" style="color:var(--blue)">Manage in Settings</a> · Shield stops requests to known tracking and advertising hosts before they leave Theseus. It does not hide leftover empty ad boxes yet. If a site misbehaves, allow it here and reload.</div>
</div>
<script>
const $ = (id) => document.getElementById(id);
const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;" })[c]);
const ago = (t) => { if (!t) return "bundled with the add-on"; const m = Math.round((Date.now() - t) / 60000); if (m < 2) return "updated just now"; if (m < 90) return `updated ${m} min ago`; const h = Math.round(m / 60); if (h < 36) return `updated ${h} h ago`; return `updated ${Math.round(h / 24)} days ago`; };
let state = null, hintHost = null;
function render(s) {
state = s;
const on = s.enabled && s.ready;
$("pill").className = "pill " + (s.enabled ? (s.ready ? "on" : "") : "off");
$("pillText").textContent = !s.enabled ? "Off" : !s.ready ? "Loading" : "On";
$("enabled").classList.toggle("on", s.enabled); $("enabled").setAttribute("aria-checked", String(s.enabled));
$("total").textContent = Number(s.blockedTotal || 0).toLocaleString();
const card = $("siteCard");
if (!s.site) {
card.innerHTML = `<div class="lbl">Open a site to see what Shield stops there.</div>`;
} else if (s.site.allowed) {
card.innerHTML = `<div class="host">${esc(s.site.host)}</div><div class="num allowed">—</div><div class="lbl">Allowed: nothing is blocked on this site.</div>
<button class="btn primary wide" id="siteBtn">Block on this site</button>`;
} else {
card.innerHTML = `<div class="host">${esc(s.site.host)}</div><div class="num">${on ? Number(s.site.blocked).toLocaleString() : "—"}</div>
<div class="lbl">${on ? (s.site.blocked === 1 ? "request blocked on this page" : "requests blocked on this page") : "Shield is off"}</div>
<div class="sub">${esc(s.site.requests)} requests seen</div>
<button class="btn wide" id="siteBtn">Allow this site</button>`;
}
const btn = $("siteBtn");
if (btn) btn.onclick = async () => {
const next = !s.site.allowed;
hintHost = s.site.host;
await window.silentmode.invoke("allow-site", { host: s.site.host, allowed: next });
};
if (s.site && hintHost === s.site.host) card.insertAdjacentHTML("beforeend", `<div class="sub">Reload the page to apply.</div>`);
else hintHost = null;
const L = s.lists || { versions: {}, titles: {} };
$("lists").innerHTML = Object.keys(L.titles || {}).map((id) => `<div class="row"><div class="k">${esc(L.titles[id])}</div><div class="v">${esc(L.versions[id] || "—")}</div></div>`).join("");
$("updated").textContent = (L.source === "online" ? "Rules " + ago(L.updatedAt) : "Rules bundled with the add-on") + (s.refreshing ? " · refreshing…" : "");
$("refresh").disabled = !!s.refreshing;
}
$("enabled").onclick = () => window.silentmode.invoke("set-enabled", { enabled: !(state && state.enabled) }).then(render);
$("toSettings").onclick = (e) => { e.preventDefault(); window.silentmode.invoke("open-settings"); };
$("refresh").onclick = () => { $("refresh").disabled = true; window.silentmode.invoke("refresh-lists").then((r) => { if (r && !r.ok) $("updated").textContent = "Update failed: " + (r.error || "unknown"); }); };
window.silentmode.on("state", render);
window.silentmode.invoke("state").then(render);
window.silentmode.onVisibility((v) => { if (v) window.silentmode.invoke("state").then(render); });
</script>
</body>
</html>