From b4030f91ddf2f3859a8de70bef652581077b31e4 Mon Sep 17 00:00:00 2001 From: Josh Patra Date: Mon, 30 Mar 2026 16:25:03 -0400 Subject: [PATCH] feat: settings update --- inject.js | 6 +- manifest.json | 2 +- options.css | 810 +++++++++++++++++++++++++++----------------------- options.html | 714 ++++++++++++++++++++++++-------------------- options.js | 20 +- 5 files changed, 839 insertions(+), 713 deletions(-) diff --git a/inject.js b/inject.js index 215451a..70f6f45 100644 --- a/inject.js +++ b/inject.js @@ -1715,13 +1715,13 @@ function applySiteRuleOverrides() { // Override general settings with site-specific overrides const siteSettings = [ "startHidden", + "hideWithControls", + "hideWithControlsTimer", "controllerLocation", "rememberSpeed", "forceLastSavedSpeed", "audioBoolean", - "controllerOpacity", - "hideWithControls", - "hideWithControlsTimer" + "controllerOpacity" ]; siteSettings.forEach((key) => { diff --git a/manifest.json b/manifest.json index b3af494..bc66268 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name": "Speeder", "short_name": "Speeder", - "version": "4.3.2", + "version": "4.3.4", "manifest_version": 2, "description": "Speed up, slow down, advance and rewind HTML5 audio/video with shortcuts (New and improved version of \"Video Speed Controller\")", "homepage_url": "https://github.com/SoPat712/speeder", diff --git a/options.css b/options.css index 0d83ac1..f77e84e 100644 --- a/options.css +++ b/options.css @@ -1,188 +1,425 @@ +:root { + --bg: #f4f5f7; + --panel: #ffffff; + --panel-subtle: #fafbfc; + --border: #e2e5e9; + --border-strong: #d4d9e0; + --text: #17191c; + --muted: #626b76; + --accent: #111827; + --danger: #b42318; +} + +* { + box-sizing: border-box; +} + +html { + min-height: 100%; +} + body { margin: 0; - padding-left: 15px; - padding-top: 80px; - font-family: sans-serif; - font-size: 12px; - color: rgb(48, 57, 66); - background-color: white; + min-height: 100vh; + padding: 24px 16px 40px; + background: var(--bg); + color: var(--text); + font: 14px/1.45 "Avenir Next", "SF Pro Text", "Segoe UI", sans-serif; +} + +.page-shell { + width: min(880px, 100%); + margin: 0 auto; +} + +.page-header { + margin-bottom: 10px; +} + +.title-row { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; +} + +.title-block { + max-width: 560px; } h1, -h2, -h3 { - font-weight: normal; - line-height: 1; - user-select: none; - cursor: default; -} -h1 { - font-size: 1.5em; - margin: 21px 0 6px; -} -.version { - margin: 0 0 6px; - color: #6b6b6b; - font-size: 0.95em; -} -h3 { - font-size: 1.2em; - margin-bottom: 0.8em; - color: black; -} -p { - margin: 0.65em 0; +h3, +h4 { + margin: 0; + font-family: "Avenir Next", "SF Pro Display", "Segoe UI", sans-serif; + font-weight: 600; + letter-spacing: -0.01em; } -header { - position: fixed; - top: 0; - left: 15px; - right: 0; - padding-bottom: 10px; - background: white; - z-index: 100; - border-bottom: 1px solid #eee; +h1 { + font-size: 28px; + line-height: 1.1; } -header, -section { - min-width: 600px; - max-width: 738px; + +h3 { + font-size: 16px; + line-height: 1.25; } -section { - padding-left: 18px; - margin-top: 8px; - margin-bottom: 24px; + +h4 { + font-size: 15px; + line-height: 1.3; } -section h3 { - margin-left: -18px; + +.page-subtitle, +.section-intro { + margin: 6px 0 0; + color: var(--muted); + font-size: 13px; +} + +.version { + display: inline-flex; + align-items: center; + min-height: 26px; + padding: 0 9px; + border: 1px solid var(--border); + border-radius: 999px; + background: var(--panel); + color: var(--muted); + font-size: 12px; + font-weight: 600; +} + +.settings-stack { + display: grid; + gap: 10px; +} + +.settings-card { + padding: 18px; + border: 1px solid var(--border); + border-radius: 12px; + background: var(--panel); +} + +.section-heading { + margin-bottom: 10px; +} + +p { + margin: 0.75em 0; +} + +a, +a:visited { + color: var(--text); + text-decoration-color: #c6ccd5; + text-underline-offset: 0.14em; +} + +a:hover, +a:focus { + color: #000; +} + +code { + padding: 0.08em 0.38em; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel-subtle); + font-family: "SFMono-Regular", Menlo, Consolas, monospace; + font-size: 0.92em; +} + +button, +input, +select, +textarea { + font: inherit; } button { - -webkit-appearance: none; appearance: none; - position: relative; - - margin: 0 1px 0 0; - padding: 0 10px; - min-width: 4em; - min-height: 2em; - - 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); - font: inherit; - - user-select: none; + min-height: 36px; + padding: 0 14px; + border: 1px solid var(--border-strong); + border-radius: 10px; + background: var(--panel); + color: var(--text); + font-weight: 500; + cursor: pointer; + transition: background-color 120ms ease, border-color 120ms ease; } -#exportSettings, -#importSettings { - background-image: linear-gradient(#e3f2fd, #e3f2fd 38%, #bbdefb); - border-color: rgba(33, 150, 243, 0.4); - color: #1976d2; +button:hover { + background: #f8f9fb; + border-color: #c5ccd5; } -input[type="text"] { - width: 75px; - text-align: center; +button:active { + background: #f1f3f5; } -.row { - margin: 5px 0px; +button:focus-visible, +input[type="text"]:focus, +select:focus, +textarea:focus { + outline: 2px solid rgba(17, 24, 39, 0.14); + outline-offset: 2px; +} + +#save { + background: var(--accent); + border-color: var(--accent); + color: #fff; +} + +#save:hover { + background: #1f2937; + border-color: #1f2937; +} + +input[type="text"], +select, +textarea { + width: 100%; + min-height: 36px; + padding: 8px 10px; + border: 1px solid var(--border-strong); + border-radius: 10px; + background: var(--panel); + color: var(--text); +} + +input[type="text"]:focus, +select:focus, +textarea:focus { + border-color: #9ca3af; +} + +input[type="checkbox"] { + width: 16px; + height: 16px; + margin: 2px 0 0; + accent-color: var(--accent); +} + +label { + display: block; +} + +label em { + display: block; + margin-top: 4px; + color: var(--muted); + font-style: normal; } .shortcuts-grid { display: flex; flex-direction: column; - gap: 5px; } .shortcut-row { - display: flex; + display: grid; + grid-template-columns: minmax(0, 1fr) 120px 120px; + gap: 12px; align-items: center; - gap: 5px; + padding: 10px 0; + border-top: 1px solid var(--border); +} + +.shortcuts-grid .shortcut-row:first-child { + padding-top: 0; + border-top: 0; +} + +.shortcut-row.customs { + grid-template-columns: minmax(0, 1fr) 120px 120px 38px; } .shortcut-label { - display: inline-block; - width: 170px; - padding: 4px 8px; - background-color: #f5f5f5; - border: 1px solid #ccc; - border-radius: 2px; - box-sizing: border-box; - font-size: 12px; - line-height: normal; - min-height: 26px; + color: var(--text); + font-weight: 500; } -.shortcut-row input[type="text"] { - height: 26px; - box-sizing: border-box; -} - -.removeParent { - width: 20px; - height: 20px; - min-width: 20px; - border-radius: 50%; - padding: 0; - display: flex !important; - align-items: center; - justify-content: center; - font-size: 10px; - font-weight: bold; - line-height: 1; - background-color: #fff; - border: 1px solid #ff4444; - color: #ff4444; - cursor: pointer; - margin-left: 5px; - transition: all 0.2s ease; -} - -.removeParent:hover { - background-color: #ffeeee; - color: #cc0000; - border-color: #cc0000; - box-shadow: 0 0 3px rgba(255, 68, 68, 0.3); +.customKey, +.customValue { + text-align: center; } #addShortcutSelector { - margin-top: 15px; - padding: 4px; - width: 200px; + width: min(220px, 100%); + margin-top: 12px; } -label { - display: inline-block; - width: 170px; - vertical-align: top; +.removeParent, +.toggle-site-rule { + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + min-width: 36px; + height: 36px; + padding: 0; + font-size: 18px; + line-height: 1; +} + +.removeParent { + color: var(--danger); + font-weight: 500; +} + +.toggle-site-rule { + font-weight: 400; +} + +.row { + display: grid; + grid-template-columns: minmax(0, 1fr) 160px; + gap: 16px; + align-items: start; + padding: 10px 0; + border-top: 1px solid var(--border); +} + +.settings-card .row:first-of-type { + padding-top: 0; + border-top: 0; +} + +#siteRulesContainer { + display: grid; + gap: 12px; + margin-bottom: 12px; +} + +.site-rule { + padding: 12px; + border: 1px solid var(--border); + border-radius: 12px; + background: var(--panel-subtle); +} + +.site-rule-header { + display: grid; + grid-template-columns: 36px minmax(0, 1fr) auto; + gap: 10px; + align-items: center; +} + +.site-pattern { + min-width: 0; + font-family: "SFMono-Regular", Menlo, Consolas, monospace; +} + +.site-rule-body { + margin-top: 12px; +} + +.site-rule-content.disabled-rule { + opacity: 0.55; + pointer-events: none; +} + +.site-rule-option { + display: grid; + grid-template-columns: minmax(0, 1fr) 150px; + gap: 16px; + align-items: start; + padding: 8px 0; + border-top: 1px solid var(--border); +} + +.site-rule-body > .site-rule-option:first-child, +.site-rule-content > .site-rule-option:first-child { + padding-top: 0; + border-top: 0; +} + +.site-rule-option label { + display: flex; + align-items: flex-start; + gap: 10px; + width: auto; +} + +.site-rule-shortcuts { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid var(--border); +} + +.site-rule-shortcuts > label { + display: flex; + align-items: flex-start; + gap: 10px; + width: auto; + margin: 0; +} + +.site-shortcuts-container { + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 12px; +} + +.site-shortcuts-container .shortcut-row { + grid-template-columns: minmax(0, 1fr) 110px 110px minmax(0, 1fr); + padding: 8px 0; + border-top: 1px solid var(--border); +} + +.site-shortcuts-container .shortcut-row:first-child { + padding-top: 0; + border-top: 0; +} + +.force-label { + display: flex; + align-items: flex-start; + gap: 8px; + width: auto; + margin: 0; + color: var(--muted); + font-size: 12px; +} + +.force-label input { + margin-top: 2px; +} + +.action-row { + display: flex; + flex-wrap: wrap; + gap: 8px; } #status { - color: #9d9d9d; - display: inline-block; - margin-left: 50px; + min-height: 1.3em; + margin-top: 10px; + color: var(--muted); + font-weight: 500; } -#faq { - margin-top: 2em; +#status:empty { + display: none; +} + +#faq hr { + height: 1px; + margin: 0 0 14px; + border: 0; + background: var(--border); } .support-footer { - min-width: 600px; - max-width: 738px; - margin: 28px 0 24px; - padding-left: 18px; - color: #6b6b6b; - font-size: 0.95em; + padding: 16px 20px; + color: var(--muted); } .support-footer p { @@ -190,268 +427,87 @@ label { } .support-footer a { - color: #2f5ca8; - text-decoration: none; + font-weight: 600; } -.support-footer a:hover, -.support-footer a:focus { - text-decoration: underline; +@media (max-width: 720px) { + .shortcut-row, + .shortcut-row.customs, + .row, + .site-rule-option, + .site-shortcuts-container .shortcut-row { + grid-template-columns: 1fr; + } + + .action-row button, + #addShortcutSelector { + width: 100%; + } + + .site-rule-header { + grid-template-columns: 36px minmax(0, 1fr); + } + + .remove-site-rule { + grid-column: 1 / -1; + } } -select { - width: 170px; -} - -.customKey { - color: transparent; - text-shadow: 0 0 0 #000000; -} - -/* Dark mode styles */ -@media (prefers-color-scheme: dark) { +@media (max-width: 520px) { body { - background-color: #1a1a1a; - color: #e0e0e0; + padding: 16px 12px 28px; } - h3 { - color: #ffffff; + h1 { + font-size: 24px; } - .version { - color: #a8a8a8; + .settings-card { + padding: 16px; } - header { - border-bottom: 1px solid #333; - background: linear-gradient(#1a1a1a, #1a1a1a 40%, rgba(26, 26, 26, 0.92)); + .site-rule-header { + grid-template-columns: 1fr; + } +} + +@media (prefers-color-scheme: dark) { + :root { + --bg: #111315; + --panel: #171a1d; + --panel-subtle: #1b1f23; + --border: #2b3138; + --border-strong: #3a414a; + --text: #f2f4f6; + --muted: #a0a8b2; + --accent: #f2f4f6; + --danger: #ff8a80; } - 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); + body { + color-scheme: dark; } - button:hover { - background-image: linear-gradient(#4a4a4a, #4a4a4a 38%, #3f3f3f); + a, + a:visited { + color: #f2f4f6; + text-decoration-color: #4b5563; } - #exportSettings, - #importSettings { - background-image: linear-gradient(#1e3a5f, #1e3a5f 38%, #152d47); - border-color: rgba(100, 181, 246, 0.4); - color: #90caf9; + #save { + background: #f2f4f6; + border-color: #f2f4f6; + color: #111315; } - input[type="text"], - input[type="checkbox"], - select, - textarea { - background-color: #2a2a2a; - color: #e0e0e0; - border: 1px solid #555; + #save:hover { + background: #dfe3e8; + border-color: #dfe3e8; } input[type="text"]:focus, select:focus, textarea:focus { - background-color: #333; - border-color: #777; - outline: none; - } - - select option { - background-color: #2a2a2a; - color: #e0e0e0; - } - - .customKey { - color: transparent; - text-shadow: 0 0 0 #e0e0e0; - } - - .shortcut-label { - background-color: #2a2a2a; - border-color: #555; - } - - #status { - color: #888; - } - - a { - color: #66b3ff; - } - - a:visited { - color: #cc99ff; - } - - .support-footer { - color: #a8a8a8; - } - - .support-footer a, - .support-footer a:visited { - color: #8bb8ff; - } - - hr { - border-color: #333; - } -} - -/* Site Rules Styles */ -#siteRulesContainer { - margin-bottom: 15px; -} - -.site-rule { - border: 1px solid #ddd; - border-radius: 4px; - padding: 12px; - margin-bottom: 12px; - background-color: #f9f9f9; -} - -.site-rule-header { - display: flex; - gap: 10px; - margin-bottom: 12px; - align-items: center; -} - -.site-rule.collapsed .site-rule-header { - margin-bottom: 0; -} - -.toggle-site-rule { - min-width: auto; - padding: 0 8px; - font-size: 12px; - cursor: pointer; -} - -.site-pattern { - flex: 1; - min-width: 300px; - padding: 6px; - font-family: monospace; -} - -.remove-site-rule { - min-width: auto; - padding: 0 12px; -} - -.site-rule-body { - margin-left: 10px; -} - -.site-rule-content.disabled-rule { - opacity: 0.5; - pointer-events: none; - filter: grayscale(1); -} - -.site-rule-option { - margin: 10px 0; - display: flex; - align-items: center; - justify-content: space-between; - max-width: 450px; -} - -.site-rule-option label { - width: auto; - line-height: normal; - display: flex; - align-items: center; - gap: 8px; -} - -.site-rule-option input[type="text"], -.site-rule-option select { - width: 150px; - text-align: left; -} - -.site-rule-shortcuts { - margin-top: 12px; - padding-top: 12px; - padding-bottom: 8px; - border-top: 1px solid #ddd; -} - -.site-rule-shortcuts > label { - display: block; - margin-bottom: 8px; - width: auto; -} - -.site-shortcuts-container { - margin-left: 20px; - margin-top: 10px; - margin-bottom: 8px; -} - -.site-shortcuts-container .shortcut-row { - margin: 6px 0; - display: flex; - align-items: center; - gap: 5px; -} - -.site-shortcuts-container .customKey { - width: 100px; -} - -.site-shortcuts-container .customValue { - width: 80px; -} - -.site-shortcuts-container .customDo { - width: 180px; -} - -.site-shortcuts-container .customForce { - width: auto; - margin-left: 5px; -} - -.site-shortcuts-container .force-label { - display: flex; - align-items: center; - gap: 4px; - margin-left: 8px; - width: auto; - font-size: 11px; - cursor: pointer; -} - -.site-shortcuts-container .force-text { - user-select: none; -} - -/* Dark mode for site rules */ -@media (prefers-color-scheme: dark) { - .site-rule { - border-color: #444; - background-color: #252525; - } - - .site-pattern { - background-color: #2a2a2a; - color: #e0e0e0; - border: 1px solid #555; - } - - .site-rule-shortcuts { - border-top-color: #444; + border-color: #6b7280; } } diff --git a/options.html b/options.html index 10e9aae..f4423e7 100644 --- a/options.html +++ b/options.html @@ -1,346 +1,416 @@ - Video Speed Controller: Options + + + Speeder Settings -
-

Video Speed Controller

-
Version
-
+
+ -
-

Shortcuts

-
-
-
Show/hide controller
- - -
-
-
Move controller
- - -
-
-
Decrease speed
- - -
-
-
Increase speed
- - -
-
-
Rewind
- - -
-
-
Advance
- - -
-
-
Reset speed
- - -
-
-
Preferred speed
- - -
-
-
Toggle subtitle nudge
- - -
-
+
+
+
+

Shortcuts

+

Backspace clears a shortcut. Escape disables it.

+
+
+
+
Show/hide controller
+ + +
+
+
Move controller
+ + +
+
+
Decrease speed
+ + +
+
+
Increase speed
+ + +
+
+
Rewind
+ + +
+
+
Advance
+ + +
+
+
Reset speed
+ + +
+
+
Preferred speed
+ + +
+
+
Toggle subtitle nudge
+ + +
+
- -
+ +
-
-

Other

-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-

Site-Specific Settings

-

- Override default settings for specific websites. Supports - Regex patterns (e.g., - /(.+)youtube\.com(\/*)$/gi). -

-
- -
- - -
-

Subtitle Nudge Settings

-
- - -
-
- - -
-
+
+
+

Subtitle sync

+

Use small speed nudges if subtitles drift.

+
+
+ + +
+
+ + +
+
- - - - +
+
+

Actions

+

Save, restore, export, or import settings.

+
+
+ + + + +
-
+
+
-
-
+
+
-

Extension controls not appearing?

-

- This extension is only compatible with HTML5 audio and video. If you - don't see the controls showing up, chances are you are viewing a Flash - content. If you want to confirm, try right-clicking on the content and - inspect the menu: if it mentions flash, then that's the issue. That - said, most sites will fallback to HTML5 if they detect that Flash - is not available. You can try manually disabling Flash from the browser. -

+

Extension controls not appearing?

+

+ This extension only works with HTML5 audio and video. If the + controls never appear, you may be looking at Flash content instead. + Right-click the player to check: if the menu mentions Flash, that + is the issue. Most sites will fall back to HTML5 when Flash is not + available, so disabling Flash in the browser can help. +

+
+ +
+

+ If Speeder has been useful, consider supporting its development via + GitHub Sponsor + or + Ko-Fi. +

+
+
- - diff --git a/options.js b/options.js index 1ba266b..3cc1457 100644 --- a/options.js +++ b/options.js @@ -239,7 +239,7 @@ function refreshAddShortcutSelector() { selector.options[0].text = "All shortcuts added"; } else { selector.disabled = false; - selector.options[0].text = "Add shortcut..."; + selector.options[0].text = "Add shortcut\u2026"; } } @@ -477,7 +477,7 @@ function add_shortcut(action, value) { var removeButton = document.createElement("button"); removeButton.className = "removeParent"; removeButton.type = "button"; - removeButton.textContent = "X"; + removeButton.textContent = "\u00d7"; div.appendChild(actionLabel); div.appendChild(keyInput); @@ -622,13 +622,13 @@ function save_options() { // Handle other site settings const siteSettings = [ { key: "startHidden", type: "checkbox" }, + { key: "hideWithControls", type: "checkbox" }, + { key: "hideWithControlsTimer", type: "text" }, { key: "controllerLocation", type: "select" }, { key: "rememberSpeed", type: "checkbox" }, { key: "forceLastSavedSpeed", type: "checkbox" }, { key: "audioBoolean", type: "checkbox" }, - { key: "controllerOpacity", type: "text" }, - { key: "hideWithControls", type: "checkbox" }, - { key: "hideWithControlsTimer", type: "text" } + { key: "controllerOpacity", type: "text" } ]; siteSettings.forEach((s) => { @@ -838,13 +838,13 @@ function createSiteRule(rule) { const settings = [ { key: "startHidden", type: "checkbox" }, + { key: "hideWithControls", type: "checkbox" }, + { key: "hideWithControlsTimer", type: "text" }, { key: "controllerLocation", type: "select" }, { key: "rememberSpeed", type: "checkbox" }, { key: "forceLastSavedSpeed", type: "checkbox" }, { key: "audioBoolean", type: "checkbox" }, - { key: "controllerOpacity", type: "text" }, - { key: "hideWithControls", type: "checkbox" }, - { key: "hideWithControlsTimer", type: "text" } + { key: "controllerOpacity", type: "text" } ]; settings.forEach((s) => { @@ -1062,11 +1062,11 @@ document.addEventListener("DOMContentLoaded", function () { if (isCollapsed) { ruleBody.style.display = "block"; ruleEl.classList.remove("collapsed"); - event.target.textContent = "-"; + event.target.textContent = "\u2212"; } else { ruleBody.style.display = "none"; ruleEl.classList.add("collapsed"); - event.target.textContent = "+"; + event.target.textContent = "\u002b"; } return; }