colorsbymax 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  What changed in each colorsbymax release. Update with `npm install colorsbymax@latest`.
4
4
 
5
+ ## 0.3.1 (2026-09-25)
6
+
7
+ - **`colourLogo` config option:** start with "Colour the logo too" on, for sites whose logo is drawn in the theme's colours. It's off by default, so logos keep their own colours, and visitors can still change it in settings. It also applies when the switcher is hidden. The colorsbymax site turns it on for its wordmark.
8
+
5
9
  ## 0.3.0 (2026-09-25)
6
10
 
7
11
  - **Dark mode for every site, on every load.** The mode now lives in the theme provider: a saved Dark (or Auto on a dark device) turns the whole site dark as soon as it loads, not only when the mode is changed, and it keeps working when the switcher is hidden. Before, the panel could go dark while the site stayed light. Sites without a dark mode of their own get one: every theme's dark twin re-colours the page, contrast-checked.
package/README.md CHANGED
@@ -326,6 +326,8 @@ Without a `siteName`, the site group is named from the page's `og:site_name`, it
326
326
  position: 'bottom-right', // where the button starts: bottom-right (default), bottom-left,
327
327
  // top-left, or top-right (just under a floating nav bar)
328
328
  hidden: import.meta.env.PROD, // hide the button (the theme still applies), e.g. in production
329
+ colourLogo: false, // themes colour the logo too, for first-time visitors (default
330
+ // false: it keeps its own colours); visitors can change it
329
331
  intro: true, // the button pops in with a burst of the theme's colours a moment
330
332
  // after the page loads (default); reduced motion fades it in
331
333
  }
@@ -1709,24 +1709,28 @@ var PANEL_PRESETS = [
1709
1709
  height: "full"
1710
1710
  }
1711
1711
  ];
1712
- /** @returns {PanelSettings} */
1713
- function loadSettings(storageKey) {
1712
+ /**
1713
+ * @param {string} storageKey
1714
+ * @param {PanelSettings} [defaults] the site's starting settings (see the config's colourLogo)
1715
+ * @returns {PanelSettings}
1716
+ */
1717
+ function loadSettings(storageKey, defaults = DEFAULT_SETTINGS) {
1714
1718
  try {
1715
1719
  const saved = JSON.parse(window.localStorage.getItem(`${storageKey}:settings`) || "null");
1716
- if (!saved || typeof saved !== "object") return DEFAULT_SETTINGS;
1717
- const out = { ...DEFAULT_SETTINGS };
1718
- for (const [key, fallback] of Object.entries(DEFAULT_SETTINGS)) if (typeof saved[key] === typeof fallback) out[key] = saved[key];
1720
+ if (!saved || typeof saved !== "object") return defaults;
1721
+ const out = { ...defaults };
1722
+ for (const [key, fallback] of Object.entries(defaults)) if (typeof saved[key] === typeof fallback) out[key] = saved[key];
1719
1723
  if (![
1720
1724
  "light",
1721
1725
  "dark",
1722
1726
  "system"
1723
- ].includes(out.mode)) out.mode = DEFAULT_SETTINGS.mode;
1727
+ ].includes(out.mode)) out.mode = defaults.mode;
1724
1728
  const size = (v) => typeof v === "number" && v > 0 && v < 1e4;
1725
1729
  out.panelWidth = size(saved.panelWidth) ? saved.panelWidth : null;
1726
1730
  out.panelHeight = size(saved.panelHeight) || saved.panelHeight === "full" ? saved.panelHeight : null;
1727
1731
  return out;
1728
1732
  } catch {
1729
- return DEFAULT_SETTINGS;
1733
+ return defaults;
1730
1734
  }
1731
1735
  }
1732
1736
  function saveSettings(storageKey, settings) {
@@ -1908,6 +1912,8 @@ var newId = () => `custom-${Date.now().toString(36)}-${Math.random().toString(36
1908
1912
  * Where each token is used on this site, shown in the editors
1909
1913
  * @property {boolean} [scrollbars] Colour the page's scrollbars from the theme (default true)
1910
1914
  * @property {() => Promise<any>} [pdf] Enables PDF uploads: pass `loadPdf` from 'colorsbymax/pdf'
1915
+ * @property {boolean} [colourLogo] Whether themes colour the site's logo for a first-time visitor
1916
+ * (default false: the logo keeps its own colours). Visitors can still change it in settings.
1911
1917
  * @property {boolean} [intro] Bring the colour button in with a short pop and burst of the theme's
1912
1918
  * colours, a moment after the page loads (default true). Reduced motion fades it in instead.
1913
1919
  * @property {boolean} [hidden] Hide the colour button and panel; the theme still applies. Use
@@ -2009,7 +2015,11 @@ function ThemeProvider({ config = {}, children }) {
2009
2015
  recolourer.current = null;
2010
2016
  };
2011
2017
  }, [recolourMode]);
2012
- const [logoColouring, setLogoColouring] = useState(false);
2018
+ const settingDefaults = useMemo(() => ({
2019
+ ...DEFAULT_SETTINGS,
2020
+ colourLogo: Boolean(initialConfig.colourLogo)
2021
+ }), [initialConfig]);
2022
+ const [logoColouring, setLogoColouring] = useState(() => loadSettings(storageKey, settingDefaults).colourLogo);
2013
2023
  useLayoutEffect(() => {
2014
2024
  recolourer.current?.setLogoColouring(logoColouring);
2015
2025
  }, [logoColouring, pageColours]);
@@ -2077,7 +2087,7 @@ function ThemeProvider({ config = {}, children }) {
2077
2087
  tokens: theme.tokens
2078
2088
  } : s.snapshot
2079
2089
  })), []);
2080
- const [modeSetting, setModeSetting] = useState(() => loadSettings(storageKey).mode);
2090
+ const [modeSetting, setModeSetting] = useState(() => loadSettings(storageKey, settingDefaults).mode);
2081
2091
  const prefersDark = usePrefersDark();
2082
2092
  const mode = modeSetting === "system" ? prefersDark ? "dark" : "light" : modeSetting;
2083
2093
  const modeRef = useRef(mode);
@@ -2090,10 +2100,10 @@ function ThemeProvider({ config = {}, children }) {
2090
2100
  ].includes(next)) return;
2091
2101
  setModeSetting(next);
2092
2102
  saveSettings(storageKey, {
2093
- ...loadSettings(storageKey),
2103
+ ...loadSettings(storageKey, settingDefaults),
2094
2104
  mode: next
2095
2105
  });
2096
- }, [storageKey]);
2106
+ }, [storageKey, settingDefaults]);
2097
2107
  useLayoutEffect(() => {
2098
2108
  if (base.custom) return;
2099
2109
  if (mode === "dark" && !isDarkTheme(base.tokens)) {
@@ -2135,6 +2145,8 @@ function ThemeProvider({ config = {}, children }) {
2135
2145
  }, [setMode]);
2136
2146
  const api = {
2137
2147
  state,
2148
+ /** The switcher's starting settings for this site. */
2149
+ settingDefaults,
2138
2150
  /** The mode in effect ('light' or 'dark'), the visitor's setting ('system' follows the device), and a setter. */
2139
2151
  mode,
2140
2152
  modeSetting,
@@ -5711,7 +5723,7 @@ function ThemeSwitcher() {
5711
5723
  return mount && createPortal(/* @__PURE__ */ jsx(Switcher, {}), mount);
5712
5724
  }
5713
5725
  function Switcher() {
5714
- const { issues, storageKey, tokens, position: requested, setLogoColouring, intro, mode, modeSetting, setMode } = useTheme();
5726
+ const { issues, storageKey, tokens, position: requested, setLogoColouring, intro, mode, modeSetting, setMode, settingDefaults } = useTheme();
5715
5727
  const position = CORNERS[requested] ? requested : "bottom-right";
5716
5728
  const [open, setOpen] = useState(false);
5717
5729
  const [arrived, setArrived] = useState(!intro);
@@ -5737,7 +5749,7 @@ function Switcher() {
5737
5749
  }, BURST_TIME);
5738
5750
  return () => clearTimeout(timer);
5739
5751
  }, [burst]);
5740
- const [settings, setSettings] = useState(() => loadSettings(storageKey));
5752
+ const [settings, setSettings] = useState(() => loadSettings(storageKey, settingDefaults));
5741
5753
  useEffect(() => setLogoColouring(settings.colourLogo), [settings.colourLogo, setLogoColouring]);
5742
5754
  const [auditOn, setAuditOn] = useState(false);
5743
5755
  const [findings, setFindings] = useState([]);
@@ -5913,7 +5925,7 @@ function Switcher() {
5913
5925
  const storeSettings = (next) => {
5914
5926
  saveSettings(storageKey, {
5915
5927
  ...next,
5916
- mode: loadSettings(storageKey).mode
5928
+ mode: loadSettings(storageKey, settingDefaults).mode
5917
5929
  });
5918
5930
  return next;
5919
5931
  };
@@ -5944,8 +5956,8 @@ function Switcher() {
5944
5956
  }));
5945
5957
  },
5946
5958
  reset: () => {
5947
- setMode(DEFAULT_SETTINGS.mode);
5948
- setSettings(storeSettings(DEFAULT_SETTINGS));
5959
+ setMode(settingDefaults.mode);
5960
+ setSettings(storeSettings(settingDefaults));
5949
5961
  },
5950
5962
  resetButton: saved ? resetPosition : null
5951
5963
  };
package/dist/auto.js CHANGED
@@ -1,4 +1,4 @@
1
- import { o as ThemeProvider, t as ThemeSwitcher } from "./ThemeSwitcher-CFtLm7PM.js";
1
+ import { o as ThemeProvider, t as ThemeSwitcher } from "./ThemeSwitcher-Bl_5Us5y.js";
2
2
  import { createElement } from "react";
3
3
  import { createRoot } from "react-dom/client";
4
4
  //#region src/auto.js
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { A as PAIRINGS, C as TOKEN_KEYS, D as MIN_CONTRAST_NON_TEXT, E as MIN_CONTRAST_LARGE_TEXT, F as contrastRatio, I as normalizeHex, M as checkTheme, N as fixAll, O as MIN_CONTRAST_TEXT, P as suggestFix, S as TOKEN_GROUPS, T as deriveAppTokens, _ as darkTokens, a as themeFromPalette, b as BASE_TOKENS, c as useTheme, d as DEFAULT_SETTINGS, f as collectColors, g as themeFromRoles, h as suggestThemes, i as rolesFromPalette, j as checkRamp, k as MIN_RAMP_STEP_DELTA_E, l as DEFAULT_STORAGE_KEY, m as inferRoles, n as coloursFromFile, o as ThemeProvider, p as detectSiteName, r as dominantColours, s as applyTokens, t as ThemeSwitcher, u as prePaintScript, v as isDarkTheme, w as completeTokens, x as PRESETS, y as toDark } from "./ThemeSwitcher-CFtLm7PM.js";
1
+ import { A as PAIRINGS, C as TOKEN_KEYS, D as MIN_CONTRAST_NON_TEXT, E as MIN_CONTRAST_LARGE_TEXT, F as contrastRatio, I as normalizeHex, M as checkTheme, N as fixAll, O as MIN_CONTRAST_TEXT, P as suggestFix, S as TOKEN_GROUPS, T as deriveAppTokens, _ as darkTokens, a as themeFromPalette, b as BASE_TOKENS, c as useTheme, d as DEFAULT_SETTINGS, f as collectColors, g as themeFromRoles, h as suggestThemes, i as rolesFromPalette, j as checkRamp, k as MIN_RAMP_STEP_DELTA_E, l as DEFAULT_STORAGE_KEY, m as inferRoles, n as coloursFromFile, o as ThemeProvider, p as detectSiteName, r as dominantColours, s as applyTokens, t as ThemeSwitcher, u as prePaintScript, v as isDarkTheme, w as completeTokens, x as PRESETS, y as toDark } from "./ThemeSwitcher-Bl_5Us5y.js";
2
2
  export { BASE_TOKENS, DEFAULT_SETTINGS, DEFAULT_STORAGE_KEY, MIN_CONTRAST_LARGE_TEXT, MIN_CONTRAST_NON_TEXT, MIN_CONTRAST_TEXT, MIN_RAMP_STEP_DELTA_E, PAIRINGS, PRESETS, TOKEN_GROUPS, TOKEN_KEYS, ThemeProvider, ThemeSwitcher, applyTokens, checkRamp, checkTheme, collectColors, coloursFromFile, completeTokens, contrastRatio, darkTokens, deriveAppTokens, detectSiteName, dominantColours, fixAll, inferRoles, isDarkTheme, normalizeHex, prePaintScript, rolesFromPalette, suggestFix, suggestThemes, themeFromPalette, themeFromRoles, toDark, useTheme };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "colorsbymax",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "A live theme switcher for any website: site themes, 700+ palettes, for light/dark modes and WCAG contrast checks.",
5
5
  "keywords": [
6
6
  "theme",
package/types/index.d.ts CHANGED
@@ -90,6 +90,11 @@ export interface ColorsByMaxConfig {
90
90
  * the page loads (default true). With reduced motion it fades in instead.
91
91
  */
92
92
  intro?: boolean
93
+ /**
94
+ * Whether themes colour the site's logo for a first-time visitor (default false: the logo keeps
95
+ * its own colours). Visitors can still change it in the panel's settings.
96
+ */
97
+ colourLogo?: boolean
93
98
  }
94
99
 
95
100
  export interface ThemeProviderProps {