From 0ca0c7cf434b404b4b4c350c9a13c3ea647dae88 Mon Sep 17 00:00:00 2001 From: StroepWafel Date: Mon, 20 Apr 2026 21:43:05 +0930 Subject: [PATCH 01/10] add handlers for individual Channels --- .gitignore | 3 +- src/seqta/ui/colors/Manager.ts | 37 ++++++++++- .../ui/colors/customThemeAdaptiveBindings.ts | 64 ++++++++++++++----- 3 files changed, 85 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index e24ea199..46e7ae3b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ betterseqtaplus-safari/ .parcel-cache .env .env.submit -dependency-graph.svg \ No newline at end of file +dependency-graph.svg +/src/resources/themes diff --git a/src/seqta/ui/colors/Manager.ts b/src/seqta/ui/colors/Manager.ts index d72c8844..2adccfbf 100644 --- a/src/seqta/ui/colors/Manager.ts +++ b/src/seqta/ui/colors/Manager.ts @@ -5,7 +5,7 @@ import { lightenAndPaleColor } from "./lightenAndPaleColor"; import ColorLuminance from "./ColorLuminance"; import { settingsState } from "@/seqta/utils/listeners/SettingsState"; import { getAdaptiveColour } from "@/seqta/utils/adaptiveThemeColour"; -import { getCustomThemeAdaptiveCssVariables } from "@/seqta/ui/colors/customThemeAdaptiveBindings"; +import { getCustomThemeAdaptiveCssVariableBindings } from "@/seqta/ui/colors/customThemeAdaptiveBindings"; import darkLogo from "@/resources/icons/betterseqta-light-full.png"; import lightLogo from "@/resources/icons/betterseqta-dark-full.png"; @@ -84,6 +84,21 @@ function cancelColorTransition() { } } +function getRepresentativeRgbChannels(s: string): { r: number; g: number; b: number } | null { + const parsedHex = parseRepresentativeHex(s); + if (!parsedHex) return null; + try { + const [r, g, b] = Color(parsedHex).rgb().array(); + return { + r: Math.round(r), + g: Math.round(g), + b: Math.round(b), + }; + } catch { + return null; + } +} + function applyColorsWith(selectedColor: string) { if (settingsState.transparencyEffects) { document.documentElement.classList.add("transparencyEffects"); @@ -129,8 +144,24 @@ function applyColorsWith(selectedColor: string) { applyProperties({ ...commonProps, ...modeProps, ...dynamicProps }); if (settingsState.selectedTheme) { - for (const name of getCustomThemeAdaptiveCssVariables()) { - setCSSVar(name, selectedColor); + const channels = getRepresentativeRgbChannels(selectedColor); + for (const binding of getCustomThemeAdaptiveCssVariableBindings()) { + if (!binding.channel) { + setCSSVar(binding.cssVarName, selectedColor); + continue; + } + + if (!channels) { + continue; + } + + if (binding.channel === "r") { + setCSSVar(binding.cssVarName, String(channels.r)); + } else if (binding.channel === "g") { + setCSSVar(binding.cssVarName, String(channels.g)); + } else { + setCSSVar(binding.cssVarName, String(channels.b)); + } } } diff --git a/src/seqta/ui/colors/customThemeAdaptiveBindings.ts b/src/seqta/ui/colors/customThemeAdaptiveBindings.ts index dba35e3f..cdd0c823 100644 --- a/src/seqta/ui/colors/customThemeAdaptiveBindings.ts +++ b/src/seqta/ui/colors/customThemeAdaptiveBindings.ts @@ -1,20 +1,49 @@ /** Tracks which author-declared CSS variables mirror the effective accent; not persisted in settings storage. */ const VALID_CUSTOM_PROP = /^--[a-zA-Z0-9_-]{1,120}$/; +const VALID_CHANNEL = /^(r|g|b)$/; -let boundNames: string[] = []; +export type AdaptiveChannel = "r" | "g" | "b"; -export function normalizeAdaptiveCssVariableNames( +export type AdaptiveCssVariableBinding = { + cssVarName: string; + channel?: AdaptiveChannel; +}; + +let boundBindings: AdaptiveCssVariableBinding[] = []; + +function parseAdaptiveBinding( + rawBinding: string, +): AdaptiveCssVariableBinding | null { + const trimmed = rawBinding.trim(); + if (!trimmed) return null; + + const [rawName, rawChannel] = trimmed.split(":", 2); + const cssVarName = rawName?.trim() ?? ""; + if (!VALID_CUSTOM_PROP.test(cssVarName)) return null; + + if (!rawChannel) return { cssVarName }; + + const channel = rawChannel.trim().toLowerCase(); + if (!VALID_CHANNEL.test(channel)) return null; + + return { cssVarName, channel: channel as AdaptiveChannel }; +} + +export function normalizeAdaptiveCssVariableBindings( names: string[] | undefined, -): string[] { +): AdaptiveCssVariableBinding[] { if (!names?.length) return []; - const out: string[] = []; + const out: AdaptiveCssVariableBinding[] = []; const seen = new Set(); + for (const raw of names) { - const s = raw.trim(); - if (!VALID_CUSTOM_PROP.test(s) || seen.has(s)) continue; - seen.add(s); - out.push(s); + const parsed = parseAdaptiveBinding(raw); + if (!parsed) continue; + const key = `${parsed.cssVarName}:${parsed.channel ?? "full"}`; + if (seen.has(key)) continue; + seen.add(key); + out.push(parsed); } return out; } @@ -22,19 +51,24 @@ export function normalizeAdaptiveCssVariableNames( export function setCustomThemeAdaptiveCssVariables( names: string[] | undefined, ): void { - for (const n of boundNames) { - document.documentElement.style.removeProperty(n); + for (const binding of boundBindings) { + document.documentElement.style.removeProperty(binding.cssVarName); } - boundNames = normalizeAdaptiveCssVariableNames(names); + boundBindings = normalizeAdaptiveCssVariableBindings(names); } +export function getCustomThemeAdaptiveCssVariableBindings(): AdaptiveCssVariableBinding[] { + return boundBindings; +} + +// Backward-compatible helper for existing callsites. export function getCustomThemeAdaptiveCssVariables(): string[] { - return boundNames; + return boundBindings.map((b) => b.cssVarName); } export function clearCustomThemeAdaptiveCssVariables(): void { - for (const n of boundNames) { - document.documentElement.style.removeProperty(n); + for (const binding of boundBindings) { + document.documentElement.style.removeProperty(binding.cssVarName); } - boundNames = []; + boundBindings = []; } From 37be31859f4ab37b8bbaff5c51f95c47192d0f34 Mon Sep 17 00:00:00 2001 From: StroepWafel Date: Mon, 20 Apr 2026 21:47:05 +0930 Subject: [PATCH 02/10] add notes --- src/seqta/utils/Openers/OpenWhatsNewPopup.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/seqta/utils/Openers/OpenWhatsNewPopup.ts b/src/seqta/utils/Openers/OpenWhatsNewPopup.ts index bbe7f386..06e44fa9 100644 --- a/src/seqta/utils/Openers/OpenWhatsNewPopup.ts +++ b/src/seqta/utils/Openers/OpenWhatsNewPopup.ts @@ -34,6 +34,9 @@ export function OpenWhatsNewPopup(onDismissed?: () => void) { const text = stringToHTML(/* html */ `
+

3.6.3 - Adaptive theme channel bindings & Alpine refresh

+
  • Added adaptive CSS variable channel bindings for custom themes: use --my-var:r, --my-var:g, or --my-var:b in adaptiveCssVariables.
  • +
  • Adaptive theme bindings now support both full-colour and single-channel values, enabling more reliable theme math without fragile CSS colour parsing.
  • 3.6.2 - Cloud backup, various fixes & SEQTA Engage support

  • BetterSEQTA Cloud: back up and restore extension settings from your account (General settings).
  • Optional automatic cloud sync if signed in (on by default).
  • From 44116edca50f0d0e48409898bb13596d27df823c Mon Sep 17 00:00:00 2001 From: StroepWafel Date: Mon, 20 Apr 2026 22:50:39 +0930 Subject: [PATCH 03/10] patch fix theme overrides for adaptive colour --- src/css/injected.scss | 5 +++-- src/seqta/ui/colors/Manager.ts | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/css/injected.scss b/src/css/injected.scss index 261552c2..9c5fe51c 100644 --- a/src/css/injected.scss +++ b/src/css/injected.scss @@ -1,3 +1,4 @@ + @use "sass:meta"; @import url("https://fonts.googleapis.com/css?family=Rubik:300,400,500,600,700"); @@ -2609,7 +2610,7 @@ body { [class*="MessageList__unread___"] { position: relative; - background: rgb(228 225 225); + background: var(--background-secondary, rgb(228 225 225)); } .dark [class*="MessageList__unread___"] { @@ -2735,7 +2736,7 @@ body { [class*="MessageList__MessageList___"] > ol > li[class*="MessageList__selected___"] { - background: rgb(228 225 225); + background: var(--background-secondary, rgb(228 225 225)); color: var(--text-primary); box-shadow: none !important; position: relative; diff --git a/src/seqta/ui/colors/Manager.ts b/src/seqta/ui/colors/Manager.ts index 2adccfbf..71740da9 100644 --- a/src/seqta/ui/colors/Manager.ts +++ b/src/seqta/ui/colors/Manager.ts @@ -165,6 +165,17 @@ function applyColorsWith(selectedColor: string) { } } + // Let themes opt-in to overriding only adaptive accent output. + // A theme can define `--adaptive-better-main` from adaptive channel bindings. + if (settingsState.selectedTheme && settingsState.adaptiveThemeColour) { + const adaptiveOverride = getComputedStyle(document.documentElement) + .getPropertyValue("--adaptive-better-main") + .trim(); + if (adaptiveOverride) { + setCSSVar("--better-main", adaptiveOverride); + } + } + let alliframes = document.getElementsByTagName("iframe"); for (let i = 0; i < alliframes.length; i++) { From fa8f36f3d5fcd5bad706ee16b8cfd14247dd6bbf Mon Sep 17 00:00:00 2001 From: StroepWafel Date: Tue, 21 Apr 2026 20:31:06 +0930 Subject: [PATCH 04/10] idk --- .cursor/skills/theme-authoring/SKILL.md | 96 ++++++++++++++++++++ src/plugins/built-in/themes/theme-manager.ts | 4 +- 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 .cursor/skills/theme-authoring/SKILL.md diff --git a/.cursor/skills/theme-authoring/SKILL.md b/.cursor/skills/theme-authoring/SKILL.md new file mode 100644 index 00000000..58de7286 --- /dev/null +++ b/.cursor/skills/theme-authoring/SKILL.md @@ -0,0 +1,96 @@ +--- +name: theme-authoring +description: Creates BetterSEQTA+ custom themes as `.theme.json` files for import or publishing to the BetterSEQTA theme store. Use when the user asks to make a new theme, adjust theme colors/CSS, or prepare a theme JSON for upload. +--- + +# Theme authoring (BetterSEQTA+) + +## Goal +Produce a valid `*.theme.json` file that BetterSEQTA+ can install (via theme import or store download). + +## Output contract +Return: +- The final theme JSON (ready to save as `my-theme.theme.json`) +- A short list of the main palette values used (accent + background + text) + +## Theme JSON schema (practical) +Required keys: +- `id`: string (stable identifier; use kebab-case, e.g. `banana-theme`) +- `name`: string (display name) +- `description`: string +- `defaultColour`: string CSS color (e.g. `rgb(...)` or `#RRGGBB`) +- `CanChangeColour`: boolean +- `CustomCSS`: string (CSS applied as a `