Actions
diff --git a/options.js b/options.js
index 3cc1457..cad80f8 100644
--- a/options.js
+++ b/options.js
@@ -132,6 +132,22 @@ var controllerLocations = [
"middle-left"
];
+var controllerButtonDefs = {
+ rewind: { icon: "\u00AB", name: "Rewind" },
+ slower: { icon: "\u2212", name: "Decrease speed" },
+ faster: { icon: "+", name: "Increase speed" },
+ advance: { icon: "\u00BB", name: "Advance" },
+ display: { icon: "\u00D7", name: "Close controller" },
+ reset: { icon: "\u21BA", name: "Reset speed" },
+ fast: { icon: "\u2605", name: "Preferred speed" },
+ nudge: { icon: "\u2713", name: "Subtitle nudge" },
+ settings: { icon: "\u2699", name: "Settings" },
+ pause: { icon: "\u23EF", name: "Pause / Play" },
+ muted: { icon: "M", name: "Mute / Unmute" },
+ mark: { icon: "\u2691", name: "Set marker" },
+ jump: { icon: "\u21E5", name: "Jump to marker" }
+};
+
function createDefaultBinding(action, key, keyCode, value) {
return {
action: action,
@@ -169,11 +185,16 @@ var tcDefaults = {
createDefaultBinding("toggleSubtitleNudge", "N", 78, 0)
],
siteRules: [
+ { pattern: "youtube.com", enabled: true, enableSubtitleNudge: true },
{ pattern: "example1.com", enabled: false },
{ pattern: "/example2\\.com/i", enabled: false },
{ pattern: "/(example3|sample3)\\.com/gi", enabled: false }
],
- enableSubtitleNudge: true,
+ controllerButtons: ["rewind", "slower", "faster", "advance", "display"],
+ showPopupControlBar: true,
+ popupMatchHoverControls: true,
+ popupControllerButtons: ["rewind", "slower", "faster", "advance", "display"],
+ enableSubtitleNudge: false,
subtitleNudgeInterval: 50,
subtitleNudgeAmount: 0.001
};
@@ -608,6 +629,13 @@ function save_options() {
settings.subtitleNudgeInterval = 1000;
}
+ settings.controllerButtons = getControlBarOrder();
+ settings.showPopupControlBar =
+ document.getElementById("showPopupControlBar").checked;
+ settings.popupMatchHoverControls =
+ document.getElementById("popupMatchHoverControls").checked;
+ settings.popupControllerButtons = getPopupControlBarOrder();
+
// Collect site rules
settings.siteRules = [];
document.querySelectorAll(".site-rule").forEach((ruleEl) => {
@@ -628,18 +656,46 @@ function save_options() {
{ key: "rememberSpeed", type: "checkbox" },
{ key: "forceLastSavedSpeed", type: "checkbox" },
{ key: "audioBoolean", type: "checkbox" },
- { key: "controllerOpacity", type: "text" }
+ { key: "controllerOpacity", type: "text" },
+ { key: "showPopupControlBar", type: "checkbox" },
+ { key: "enableSubtitleNudge", type: "checkbox" },
+ { key: "subtitleNudgeInterval", type: "text" }
];
siteSettings.forEach((s) => {
var input = ruleEl.querySelector(`.site-${s.key}`);
+ if (!input) return;
+ var siteValue;
if (s.type === "checkbox") {
- rule[s.key] = input.checked;
+ siteValue = input.checked;
} else {
- rule[s.key] = input.value;
+ siteValue = input.value;
+ }
+ var globalInput = document.getElementById(s.key);
+ if (globalInput) {
+ var globalValue = s.type === "checkbox" ? globalInput.checked : globalInput.value;
+ if (String(siteValue) !== String(globalValue)) {
+ rule[s.key] = siteValue;
+ }
+ } else {
+ rule[s.key] = siteValue;
}
});
+ if (ruleEl.querySelector(".override-controlbar").checked) {
+ var activeZone = ruleEl.querySelector(".site-cb-active");
+ if (activeZone) {
+ rule.controllerButtons = readControlBarOrder(activeZone);
+ }
+ }
+
+ if (ruleEl.querySelector(".override-popup-controlbar").checked) {
+ var popupActiveZone = ruleEl.querySelector(".site-popup-cb-active");
+ if (popupActiveZone) {
+ rule.popupControllerButtons = readControlBarOrder(popupActiveZone);
+ }
+ }
+
if (ruleEl.querySelector(".override-shortcuts").checked) {
var shortcuts = [];
ruleEl.querySelectorAll(".site-shortcuts-container .customs").forEach((shortcutRow) => {
@@ -844,7 +900,10 @@ function createSiteRule(rule) {
{ key: "rememberSpeed", type: "checkbox" },
{ key: "forceLastSavedSpeed", type: "checkbox" },
{ key: "audioBoolean", type: "checkbox" },
- { key: "controllerOpacity", type: "text" }
+ { key: "controllerOpacity", type: "text" },
+ { key: "showPopupControlBar", type: "checkbox" },
+ { key: "enableSubtitleNudge", type: "checkbox" },
+ { key: "subtitleNudgeInterval", type: "text" }
];
settings.forEach((s) => {
@@ -873,6 +932,28 @@ function createSiteRule(rule) {
}
});
+ if (rule && Array.isArray(rule.controllerButtons) && rule.controllerButtons.length > 0) {
+ ruleEl.querySelector(".override-controlbar").checked = true;
+ var cbContainer = ruleEl.querySelector(".site-controlbar-container");
+ cbContainer.style.display = "block";
+ populateControlBarZones(
+ ruleEl.querySelector(".site-cb-active"),
+ ruleEl.querySelector(".site-cb-available"),
+ rule.controllerButtons
+ );
+ }
+
+ if (rule && Array.isArray(rule.popupControllerButtons) && rule.popupControllerButtons.length > 0) {
+ ruleEl.querySelector(".override-popup-controlbar").checked = true;
+ var popupCbContainer = ruleEl.querySelector(".site-popup-controlbar-container");
+ popupCbContainer.style.display = "block";
+ populateControlBarZones(
+ ruleEl.querySelector(".site-popup-cb-active"),
+ ruleEl.querySelector(".site-popup-cb-available"),
+ rule.popupControllerButtons
+ );
+ }
+
if (rule && Array.isArray(rule.shortcuts) && rule.shortcuts.length > 0) {
ruleEl.querySelector(".override-shortcuts").checked = true;
var container = ruleEl.querySelector(".site-shortcuts-container");
@@ -898,6 +979,164 @@ function populateDefaultSiteShortcuts(container) {
});
}
+function createControlBarBlock(buttonId) {
+ var def = controllerButtonDefs[buttonId];
+ if (!def) return null;
+
+ var block = document.createElement("div");
+ block.className = "cb-block";
+ block.dataset.buttonId = buttonId;
+ block.draggable = true;
+
+ var grip = document.createElement("span");
+ grip.className = "cb-grip";
+
+ var icon = document.createElement("span");
+ icon.className = "cb-icon";
+ icon.textContent = def.icon;
+
+ var label = document.createElement("span");
+ label.className = "cb-label";
+ label.textContent = def.name;
+
+ block.appendChild(grip);
+ block.appendChild(icon);
+ block.appendChild(label);
+
+ return block;
+}
+
+function populateControlBarZones(activeZone, availableZone, activeIds) {
+ activeZone.innerHTML = "";
+ availableZone.innerHTML = "";
+
+ activeIds.forEach(function (id) {
+ var block = createControlBarBlock(id);
+ if (block) activeZone.appendChild(block);
+ });
+
+ Object.keys(controllerButtonDefs).forEach(function (id) {
+ if (!activeIds.includes(id)) {
+ var block = createControlBarBlock(id);
+ if (block) availableZone.appendChild(block);
+ }
+ });
+}
+
+function readControlBarOrder(activeZone) {
+ var blocks = activeZone.querySelectorAll(".cb-block");
+ return Array.from(blocks).map(function (block) {
+ return block.dataset.buttonId;
+ });
+}
+
+function populateControlBarEditor(activeIds) {
+ populateControlBarZones(
+ document.getElementById("controlBarActive"),
+ document.getElementById("controlBarAvailable"),
+ activeIds
+ );
+}
+
+function getControlBarOrder() {
+ return readControlBarOrder(document.getElementById("controlBarActive"));
+}
+
+function populatePopupControlBarEditor(activeIds) {
+ populateControlBarZones(
+ document.getElementById("popupControlBarActive"),
+ document.getElementById("popupControlBarAvailable"),
+ activeIds
+ );
+}
+
+function getPopupControlBarOrder() {
+ return readControlBarOrder(document.getElementById("popupControlBarActive"));
+}
+
+function updatePopupEditorDisabledState() {
+ var checkbox = document.getElementById("popupMatchHoverControls");
+ var wrap = document.getElementById("popupCbEditorWrap");
+ if (!checkbox || !wrap) return;
+ if (checkbox.checked) {
+ wrap.classList.add("cb-editor-disabled");
+ } else {
+ wrap.classList.remove("cb-editor-disabled");
+ }
+}
+
+function getDragAfterElement(container, x, y) {
+ var elements = Array.from(
+ container.querySelectorAll(".cb-block:not(.cb-dragging)")
+ );
+
+ for (var i = 0; i < elements.length; i++) {
+ var box = elements[i].getBoundingClientRect();
+ var centerX = box.left + box.width / 2;
+ var centerY = box.top + box.height / 2;
+ var rowThresh = box.height * 0.5;
+
+ if (y - centerY > rowThresh) continue;
+ if (centerY - y > rowThresh) return elements[i];
+ if (x < centerX) return elements[i];
+ }
+
+ return undefined;
+}
+
+function initControlBarEditor() {
+ var zones = document.querySelectorAll(".cb-dropzone");
+ var draggedBlock = null;
+
+ document.addEventListener("dragstart", function (e) {
+ var block = e.target.closest(".cb-block");
+ if (!block) return;
+ draggedBlock = block;
+ e.dataTransfer.effectAllowed = "move";
+ e.dataTransfer.setData("text/plain", block.dataset.buttonId);
+ requestAnimationFrame(function () {
+ block.classList.add("cb-dragging");
+ });
+ });
+
+ document.addEventListener("dragend", function (e) {
+ var block = e.target.closest(".cb-block");
+ if (!block) return;
+ block.classList.remove("cb-dragging");
+ draggedBlock = null;
+ zones.forEach(function (zone) {
+ zone.classList.remove("cb-over");
+ });
+ });
+
+ zones.forEach(function (zone) {
+ zone.addEventListener("dragover", function (e) {
+ e.preventDefault();
+ e.dataTransfer.dropEffect = "move";
+ zone.classList.add("cb-over");
+
+ if (!draggedBlock) return;
+
+ var afterEl = getDragAfterElement(zone, e.clientX, e.clientY);
+ if (afterEl) {
+ zone.insertBefore(draggedBlock, afterEl);
+ } else {
+ zone.appendChild(draggedBlock);
+ }
+ });
+
+ zone.addEventListener("dragleave", function (e) {
+ if (zone.contains(e.relatedTarget)) return;
+ zone.classList.remove("cb-over");
+ });
+
+ zone.addEventListener("drop", function (e) {
+ e.preventDefault();
+ zone.classList.remove("cb-over");
+ });
+ });
+}
+
function restore_options() {
chrome.storage.sync.get(tcDefaults, function (storage) {
document.getElementById("rememberSpeed").checked = storage.rememberSpeed;
@@ -906,7 +1145,7 @@ function restore_options() {
document.getElementById("audioBoolean").checked = storage.audioBoolean;
document.getElementById("enabled").checked = storage.enabled;
document.getElementById("startHidden").checked = storage.startHidden;
-
+
// Migration/Normalization for hideWithControls
const hideWithControls = typeof storage.hideWithControls !== "undefined"
? storage.hideWithControls
@@ -920,6 +1159,8 @@ function restore_options() {
normalizeControllerLocation(storage.controllerLocation);
document.getElementById("controllerOpacity").value =
storage.controllerOpacity;
+ document.getElementById("showPopupControlBar").checked =
+ storage.showPopupControlBar !== false;
document.getElementById("enableSubtitleNudge").checked =
storage.enableSubtitleNudge;
document.getElementById("subtitleNudgeInterval").value =
@@ -981,6 +1222,24 @@ function restore_options() {
}
});
}
+
+ var controllerButtons =
+ Array.isArray(storage.controllerButtons) &&
+ storage.controllerButtons.length > 0
+ ? storage.controllerButtons
+ : tcDefaults.controllerButtons;
+ populateControlBarEditor(controllerButtons);
+
+ document.getElementById("popupMatchHoverControls").checked =
+ storage.popupMatchHoverControls !== false;
+
+ var popupButtons =
+ Array.isArray(storage.popupControllerButtons) &&
+ storage.popupControllerButtons.length > 0
+ ? storage.popupControllerButtons
+ : tcDefaults.popupControllerButtons;
+ populatePopupControlBarEditor(popupButtons);
+ updatePopupEditorDisabledState();
});
}
@@ -1005,6 +1264,11 @@ document.addEventListener("DOMContentLoaded", function () {
}
restore_options();
+ initControlBarEditor();
+
+ document.getElementById("popupMatchHoverControls")
+ .addEventListener("change", updatePopupEditorDisabledState);
+
document.getElementById("save").addEventListener("click", save_options);
const addSelector = document.getElementById("addShortcutSelector");
@@ -1096,5 +1360,43 @@ document.addEventListener("DOMContentLoaded", function () {
container.style.display = "none";
}
}
+
+ if (event.target.classList.contains("override-controlbar")) {
+ var cbContainer = event.target
+ .closest(".site-rule-controlbar")
+ .querySelector(".site-controlbar-container");
+ if (event.target.checked) {
+ cbContainer.style.display = "block";
+ var activeZone = cbContainer.querySelector(".site-cb-active");
+ if (activeZone && activeZone.children.length === 0) {
+ populateControlBarZones(
+ activeZone,
+ cbContainer.querySelector(".site-cb-available"),
+ getControlBarOrder()
+ );
+ }
+ } else {
+ cbContainer.style.display = "none";
+ }
+ }
+
+ if (event.target.classList.contains("override-popup-controlbar")) {
+ var popupCbContainer = event.target
+ .closest(".site-rule-controlbar")
+ .querySelector(".site-popup-controlbar-container");
+ if (event.target.checked) {
+ popupCbContainer.style.display = "block";
+ var popupActiveZone = popupCbContainer.querySelector(".site-popup-cb-active");
+ if (popupActiveZone && popupActiveZone.children.length === 0) {
+ populateControlBarZones(
+ popupActiveZone,
+ popupCbContainer.querySelector(".site-popup-cb-available"),
+ getPopupControlBarOrder()
+ );
+ }
+ } else {
+ popupCbContainer.style.display = "none";
+ }
+ }
});
});
diff --git a/popup.css b/popup.css
index 07421fe..5a1a08f 100644
--- a/popup.css
+++ b/popup.css
@@ -1,79 +1,253 @@
-body {
- min-width: 8em;
- background-color: white;
- color: #333;
-}
-
-.version {
- margin-top: 0.7em;
- font-size: 0.85em;
- text-align: center;
- color: #666;
-}
+:root {
+ --bg: #f4f5f7;
+ --panel: #ffffff;
+ --border: #e2e5e9;
+ --border-strong: #d4d9e0;
+ --text: #17191c;
+ --muted: #626b76;
+ --accent: #111827;
+}
-hr {
- width: 100%;
- border: 0;
- height: 0;
- border-top: 1px solid rgba(0, 0, 0, 0.3);
- margin: 0.6em 0;
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ min-width: 220px;
+ background: var(--bg);
+ color: var(--text);
+ font: 13px/1.45 "Avenir Next", "SF Pro Text", "Segoe UI", sans-serif;
+ padding: 12px;
+}
+
+.popup-shell {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.popup-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-bottom: 8px;
+ border-bottom: 1px solid var(--border);
+}
+
+.popup-title {
+ font-weight: 600;
+ font-size: 15px;
+ font-family: "Avenir Next", "SF Pro Display", "Segoe UI", sans-serif;
+ letter-spacing: -0.01em;
+}
+
+.popup-version {
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--muted);
+ padding: 2px 7px;
+ border: 1px solid var(--border);
+ border-radius: 999px;
+ background: var(--panel);
+}
+
+.popup-actions {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
}
button {
+ appearance: none;
width: 100%;
- background-image: linear-gradient(#ededed, #ededed 38%, #dedede);
- border: 1px solid rgba(0, 0, 0, 0.25);
- border-radius: 2px;
- outline: none;
- box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08),
- inset 0 1px 2px rgba(255, 255, 255, 0.75);
- color: #444;
- text-shadow: 0 1px 0 rgb(240, 240, 240);
+ min-height: 32px;
+ padding: 0 12px;
+ border: 1px solid var(--border-strong);
+ border-radius: 8px;
+ background: var(--panel);
+ color: var(--text);
font: inherit;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background 120ms ease, border-color 120ms ease;
+}
+
+button:hover {
+ background: #f8f9fb;
+ border-color: #c5ccd5;
+}
+
+button:active {
+ background: #f1f3f5;
+}
+
+button:focus-visible {
+ outline: 2px solid rgba(17, 24, 39, 0.14);
+ outline-offset: 2px;
+}
+
+#refresh {
+ background: var(--accent);
+ border-color: var(--accent);
+ color: #fff;
+}
+
+#refresh:hover {
+ background: #1f2937;
+ border-color: #1f2937;
+}
+
+.popup-divider {
+ height: 1px;
+ background: var(--border);
+ margin: 2px 0;
+}
+
+.popup-control-bar {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ padding: 6px 10px;
+ background: var(--panel);
+ border: 1px solid var(--border-strong);
+ border-radius: 8px;
+}
+
+.popup-speed {
+ font-family: "Lucida Console", Monaco, monospace;
+ font-size: 13px;
+ font-weight: bold;
+ color: var(--text);
+ margin-right: 4px;
+ line-height: 1;
user-select: none;
+ white-space: nowrap;
+}
+
+.popup-control-bar button {
+ width: auto;
+ min-height: 24px;
+ border: 1px solid var(--border-strong);
+ border-radius: 6px;
+ background: var(--bg);
+ color: var(--text);
+ font-family: "Lucida Console", Monaco, monospace;
+ font-size: 13px;
+ line-height: 13px;
+ font-weight: bold;
+ padding: 3px 7px;
+ cursor: pointer;
+}
+
+.popup-control-bar button:hover {
+ background: var(--panel);
+ border-color: var(--border-strong);
+}
+
+.popup-control-bar button:active {
+ background: var(--bg);
+}
+
+.popup-control-bar button.rw {
+ opacity: 0.55;
+}
+
+.popup-control-bar button.hideButton {
+ opacity: 0.55;
+}
+
+.popup-status {
+ font-size: 12px;
+ color: var(--muted);
+ font-weight: 500;
+ text-align: center;
+ padding: 2px 0;
+}
+
+.popup-links {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ padding-top: 8px;
+ border-top: 1px solid var(--border);
+}
+
+.popup-secondary {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 6px;
}
.secondary {
- font-size: 0.95em;
- margin: 0.15em 0;
+ font-size: 12px;
+ min-height: 28px;
+ color: var(--muted);
+}
+
+.donate-wrap {
+ display: grid;
+ grid-template-columns: 1fr;
+}
+
+.donate-split {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+}
+
+.donate-split button {
+ width: auto;
+ border-radius: 0;
+ min-height: 28px;
+}
+
+.donate-split button:first-child {
+ border-radius: 8px 0 0 8px;
+ border-right: 0;
+}
+
+.donate-split button:last-child {
+ border-radius: 0 8px 8px 0;
}
.hide {
- display: none;
+ display: none !important;
}
-/* Dark mode styles */
+
@media (prefers-color-scheme: dark) {
+ :root {
+ --bg: #111315;
+ --panel: #171a1d;
+ --border: #2b3138;
+ --border-strong: #3a414a;
+ --text: #f2f4f6;
+ --muted: #a0a8b2;
+ --accent: #f2f4f6;
+ }
+
body {
- background-color: #1a1a1a;
- color: #e0e0e0;
- }
-
- hr {
- border-top: 1px solid rgba(255, 255, 255, 0.3);
- }
-
- button {
- background-image: linear-gradient(#404040, #404040 38%, #353535);
- border: 1px solid rgba(255, 255, 255, 0.25);
- box-shadow: 0 1px 0 rgba(255, 255, 255, 0.08),
- inset 0 1px 2px rgba(0, 0, 0, 0.75);
- color: #e0e0e0;
- text-shadow: 0 1px 0 rgb(20, 20, 20);
+ color-scheme: dark;
}
button:hover {
- background-image: linear-gradient(#4a4a4a, #4a4a4a 38%, #3f3f3f);
+ background: #1f2226;
+ border-color: #4a515a;
}
button:active {
- background-image: linear-gradient(#353535, #353535 38%, #2a2a2a);
+ background: #252a2f;
}
- #status {
- color: #ccc;
- }
-
- .version {
- color: #aaa;
- }
-}
+ #refresh {
+ background: #f2f4f6;
+ border-color: #f2f4f6;
+ color: #111315;
+ }
+
+ #refresh:hover {
+ background: #dfe3e8;
+ border-color: #dfe3e8;
+ }
+}
diff --git a/popup.html b/popup.html
index ce614b3..58537bc 100644
--- a/popup.html
+++ b/popup.html
@@ -1,21 +1,42 @@
-
Video Speed Controller: Popup
+
+
Speeder
-
-
-
-
-
-
-
-
-
-
-
Version
+
diff --git a/popup.js b/popup.js
index 9b89347..a077ca9 100644
--- a/popup.js
+++ b/popup.js
@@ -1,6 +1,23 @@
document.addEventListener("DOMContentLoaded", function () {
var regStrip = /^[\r\t\f\v ]+|[\r\t\f\v ]+$/gm;
+ var controllerButtonDefs = {
+ rewind: { label: "\u00AB", className: "rw" },
+ slower: { label: "\u2212", className: "" },
+ faster: { label: "+", className: "" },
+ advance: { label: "\u00BB", className: "rw" },
+ display: { label: "\u00D7", className: "hideButton" },
+ reset: { label: "\u21BA", className: "" },
+ fast: { label: "\u2605", className: "" },
+ settings: { label: "\u2699", className: "" },
+ pause: { label: "\u23EF", className: "" },
+ muted: { label: "M", className: "" },
+ mark: { label: "\u2691", className: "" },
+ jump: { label: "\u21E5", className: "" }
+ };
+
+ var defaultButtons = ["rewind", "slower", "faster", "advance", "display"];
+
function escapeStringRegExp(str) {
const m = /[|\\{}()[\]^$+*?.]/g;
return str.replace(m, "\\$&");
@@ -27,6 +44,102 @@ document.addEventListener("DOMContentLoaded", function () {
return b;
}
+ function matchSiteRule(url, siteRules) {
+ if (!url || !Array.isArray(siteRules)) return null;
+ for (var i = 0; i < siteRules.length; i++) {
+ var rule = siteRules[i];
+ if (!rule || !rule.pattern) continue;
+ var pattern = rule.pattern.replace(regStrip, "");
+ if (pattern.length === 0) continue;
+ var re;
+ if (pattern.startsWith("/") && pattern.lastIndexOf("/") > 0) {
+ try {
+ var ls = pattern.lastIndexOf("/");
+ re = new RegExp(pattern.substring(1, ls), pattern.substring(ls + 1));
+ } catch (e) {
+ continue;
+ }
+ } else {
+ re = new RegExp(escapeStringRegExp(pattern));
+ }
+ if (re && re.test(url)) return rule;
+ }
+ return null;
+ }
+
+ function setControlBarVisible(visible) {
+ var bar = document.getElementById("popupControlBar");
+ var dividers = document.querySelectorAll(".popup-divider");
+ if (bar) bar.style.display = visible ? "" : "none";
+ dividers.forEach(function (d) { d.style.display = visible ? "" : "none"; });
+ }
+
+ function sendToActiveTab(message, callback) {
+ chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
+ if (tabs[0] && tabs[0].id) {
+ chrome.tabs.sendMessage(tabs[0].id, message, function (response) {
+ if (chrome.runtime.lastError) {
+ if (callback) callback(null);
+ } else {
+ if (callback) callback(response);
+ }
+ });
+ } else {
+ if (callback) callback(null);
+ }
+ });
+ }
+
+ function updateSpeedDisplay(speed) {
+ var el = document.getElementById("popupSpeed");
+ if (el) el.textContent = (speed != null ? Number(speed) : 1).toFixed(2);
+ }
+
+ function querySpeed() {
+ sendToActiveTab({ action: "get_speed" }, function (response) {
+ if (response && response.speed != null) {
+ updateSpeedDisplay(response.speed);
+ }
+ });
+ }
+
+ function buildControlBar(buttons) {
+ var bar = document.getElementById("popupControlBar");
+ if (!bar) return;
+
+ var existing = bar.querySelectorAll("button");
+ existing.forEach(function (btn) { btn.remove(); });
+
+ buttons.forEach(function (btnId) {
+ if (btnId === "nudge") return;
+ var def = controllerButtonDefs[btnId];
+ if (!def) return;
+
+ var btn = document.createElement("button");
+ btn.dataset.action = btnId;
+ btn.textContent = def.label;
+ if (def.className) btn.className = def.className;
+ btn.title = btnId.charAt(0).toUpperCase() + btnId.slice(1);
+
+ btn.addEventListener("click", function () {
+ if (btnId === "settings") {
+ window.open(chrome.runtime.getURL("options.html"));
+ return;
+ }
+ sendToActiveTab(
+ { action: "run_action", actionName: btnId },
+ function (response) {
+ if (response && response.speed != null) {
+ updateSpeedDisplay(response.speed);
+ }
+ }
+ );
+ });
+
+ bar.appendChild(btn);
+ });
+ }
+
var manifest = chrome.runtime.getManifest();
var versionElement = document.querySelector("#app-version");
if (versionElement) {
@@ -45,6 +158,19 @@ document.addEventListener("DOMContentLoaded", function () {
window.open("https://github.com/SoPat712/Speeder/issues");
});
+ document.querySelector("#donate").addEventListener("click", function () {
+ this.classList.add("hide");
+ document.querySelector("#donateOptions").classList.remove("hide");
+ });
+
+ document.querySelector("#donateKofi").addEventListener("click", function () {
+ window.open("https://ko-fi.com/joshpatra");
+ });
+
+ document.querySelector("#donateGithub").addEventListener("click", function () {
+ window.open("https://github.com/sponsors/SoPat712");
+ });
+
document.querySelector("#enable").addEventListener("click", function () {
toggleEnabled(true, settingsSavedReloadMessage);
});
@@ -53,27 +179,16 @@ document.addEventListener("DOMContentLoaded", function () {
toggleEnabled(false, settingsSavedReloadMessage);
});
- // --- REVISED: "Re-scan" button functionality ---
document.querySelector("#refresh").addEventListener("click", function () {
- setStatusMessage("Re-scanning page...");
- chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
- if (tabs[0] && tabs[0].id) {
- // Send a message to the content script, asking it to re-initialize.
- chrome.tabs.sendMessage(
- tabs[0].id,
- { action: "rescan_page" },
- function (response) {
- if (chrome.runtime.lastError) {
- // This error is expected on pages where content scripts cannot run.
- setStatusMessage("Cannot run on this page.");
- } else if (response && response.status === "complete") {
- setStatusMessage("Scan complete. Closing...");
- setTimeout(() => window.close(), 500); // Close popup on success.
- } else {
- setStatusMessage("Scan failed. Please reload the page.");
- }
- }
- );
+ setStatusMessage("Rescanning page...");
+ sendToActiveTab({ action: "rescan_page" }, function (response) {
+ if (!response) {
+ setStatusMessage("Cannot run on this page.");
+ } else if (response.status === "complete") {
+ setStatusMessage("Scan complete. Closing...");
+ setTimeout(function () { window.close(); }, 500);
+ } else {
+ setStatusMessage("Scan failed. Please reload the page.");
}
});
});
@@ -81,6 +196,11 @@ document.addEventListener("DOMContentLoaded", function () {
chrome.storage.sync.get(
{
enabled: true,
+ showPopupControlBar: true,
+ controllerButtons: defaultButtons,
+ popupMatchHoverControls: true,
+ popupControllerButtons: defaultButtons,
+ siteRules: [],
blacklist: `\
www.instagram.com
twitter.com
@@ -97,20 +217,38 @@ document.addEventListener("DOMContentLoaded", function () {
if (blacklisted) {
setStatusMessage("Site is blacklisted.");
}
+
+ var siteRule = matchSiteRule(url, storage.siteRules);
+
+ var buttons = storage.popupMatchHoverControls
+ ? storage.controllerButtons
+ : storage.popupControllerButtons;
+
+ if (siteRule && Array.isArray(siteRule.popupControllerButtons) && siteRule.popupControllerButtons.length > 0) {
+ buttons = siteRule.popupControllerButtons;
+ }
+
+ if (!Array.isArray(buttons) || buttons.length === 0) {
+ buttons = defaultButtons;
+ }
+
+ buildControlBar(buttons);
+ querySpeed();
+
+ var showBar = storage.showPopupControlBar !== false;
+ if (siteRule && siteRule.showPopupControlBar !== undefined) {
+ showBar = siteRule.showPopupControlBar;
+ }
+ setControlBarVisible(showBar);
});
}
);
function toggleEnabled(enabled, callback) {
- chrome.storage.sync.set(
- {
- enabled: enabled
- },
- function () {
- toggleEnabledUI(enabled);
- if (callback) callback(enabled);
- }
- );
+ chrome.storage.sync.set({ enabled: enabled }, function () {
+ toggleEnabledUI(enabled);
+ if (callback) callback(enabled);
+ });
}
function toggleEnabledUI(enabled) {
diff --git a/shadow.css b/shadow.css
index f1112d3..0e391f7 100644
--- a/shadow.css
+++ b/shadow.css
@@ -61,15 +61,13 @@
vertical-align: middle;
align-items: center;
justify-content: center;
- min-width: 1.35em;
- height: 1.35em;
margin-left: 0.3em;
- padding: 0 0.25em;
- border-radius: 999px;
- border: 1px solid rgba(255, 255, 255, 0.28);
- font-size: 12px;
+ padding: 3px 6px;
+ border-radius: 5px;
+ font-size: 14px;
+ line-height: 14px;
font-weight: bold;
- line-height: 1;
+ font-family: "Lucida Console", Monaco, monospace;
box-sizing: border-box;
}
@@ -83,49 +81,46 @@
}
#nudge-flash-indicator[data-enabled="true"] {
- color: #bff3a2;
- background: rgba(75, 145, 53, 0.28);
- border-color: rgba(126, 199, 104, 0.7);
+ color: #fff;
+ background: #4b9135;
+ border: 1px solid #6ec754;
}
#nudge-flash-indicator[data-enabled="false"] {
- color: #ffb8b8;
- background: rgba(164, 73, 73, 0.24);
- border-color: rgba(214, 118, 118, 0.65);
+ color: #fff;
+ background: #943e3e;
+ border: 1px solid #c06060;
}
#nudge-indicator {
display: inline-flex;
- vertical-align: middle;
align-items: center;
justify-content: center;
- min-width: 1.35em;
- height: 1.35em;
- margin-left: 0.45em;
- padding: 0 0.25em;
- border-radius: 999px;
- border: 1px solid rgba(255, 255, 255, 0.28);
- font-size: 12px;
+ padding: 3px 6px;
+ border-radius: 5px;
+ font-size: 14px;
+ line-height: 14px;
font-weight: bold;
- line-height: 1;
+ font-family: "Lucida Console", Monaco, monospace;
box-sizing: border-box;
cursor: pointer;
+ margin-bottom: 2px;
}
#nudge-indicator[data-enabled="true"] {
- color: #bff3a2;
- background: rgba(75, 145, 53, 0.28);
- border-color: rgba(126, 199, 104, 0.7);
+ color: #fff;
+ background: #4b9135;
+ border: 1px solid #6ec754;
}
#nudge-indicator[data-enabled="false"] {
- color: #ffb8b8;
- background: rgba(164, 73, 73, 0.24);
- border-color: rgba(214, 118, 118, 0.65);
+ color: #fff;
+ background: #943e3e;
+ border: 1px solid #c06060;
}
#nudge-indicator[data-supported="false"] {
- opacity: 0.75;
+ opacity: 0.6;
}
#controller.dragging {