colorsbymax 0.3.0 → 0.3.2

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,15 @@
2
2
 
3
3
  What changed in each colorsbymax release. Update with `npm install colorsbymax@latest`.
4
4
 
5
+ ## 0.3.2 (2026-09-25)
6
+
7
+ - **`defaultMode` config option:** the mode first-time visitors start in, `'light'` (the default), `'dark'` or `'system'` to follow their device. Any site can start dark, since every theme has a dark twin, and visitors can still switch. The colorsbymax site now opens in dark mode.
8
+ - The colorsbymax site's browser-tab icon is the mrmaxdesigns logo mark in the current theme's colours, with a version for light tabs and one for dark tabs, each checked to stand out.
9
+
10
+ ## 0.3.1 (2026-09-25)
11
+
12
+ - **`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.
13
+
5
14
  ## 0.3.0 (2026-09-25)
6
15
 
7
16
  - **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,10 @@ 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
+ defaultMode: 'light', // the mode first-time visitors start in: 'light' (default),
330
+ // 'dark', or 'system' to follow their device
331
+ colourLogo: false, // themes colour the logo too, for first-time visitors (default
332
+ // false: it keeps its own colours); visitors can change it
329
333
  intro: true, // the button pops in with a burst of the theme's colours a moment
330
334
  // after the page loads (default); reduced motion fades it in
331
335
  }
@@ -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,10 @@ 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 {'light' | 'dark' | 'system'} [defaultMode] The mode a first-time visitor starts in
1916
+ * (default 'light'; 'system' follows their device). Visitors can still change it.
1917
+ * @property {boolean} [colourLogo] Whether themes colour the site's logo for a first-time visitor
1918
+ * (default false: the logo keeps its own colours). Visitors can still change it in settings.
1911
1919
  * @property {boolean} [intro] Bring the colour button in with a short pop and burst of the theme's
1912
1920
  * colours, a moment after the page loads (default true). Reduced motion fades it in instead.
1913
1921
  * @property {boolean} [hidden] Hide the colour button and panel; the theme still applies. Use
@@ -2009,7 +2017,16 @@ function ThemeProvider({ config = {}, children }) {
2009
2017
  recolourer.current = null;
2010
2018
  };
2011
2019
  }, [recolourMode]);
2012
- const [logoColouring, setLogoColouring] = useState(false);
2020
+ const settingDefaults = useMemo(() => ({
2021
+ ...DEFAULT_SETTINGS,
2022
+ colourLogo: Boolean(initialConfig.colourLogo),
2023
+ mode: [
2024
+ "light",
2025
+ "dark",
2026
+ "system"
2027
+ ].includes(initialConfig.defaultMode) ? initialConfig.defaultMode : DEFAULT_SETTINGS.mode
2028
+ }), [initialConfig]);
2029
+ const [logoColouring, setLogoColouring] = useState(() => loadSettings(storageKey, settingDefaults).colourLogo);
2013
2030
  useLayoutEffect(() => {
2014
2031
  recolourer.current?.setLogoColouring(logoColouring);
2015
2032
  }, [logoColouring, pageColours]);
@@ -2077,7 +2094,7 @@ function ThemeProvider({ config = {}, children }) {
2077
2094
  tokens: theme.tokens
2078
2095
  } : s.snapshot
2079
2096
  })), []);
2080
- const [modeSetting, setModeSetting] = useState(() => loadSettings(storageKey).mode);
2097
+ const [modeSetting, setModeSetting] = useState(() => loadSettings(storageKey, settingDefaults).mode);
2081
2098
  const prefersDark = usePrefersDark();
2082
2099
  const mode = modeSetting === "system" ? prefersDark ? "dark" : "light" : modeSetting;
2083
2100
  const modeRef = useRef(mode);
@@ -2090,10 +2107,10 @@ function ThemeProvider({ config = {}, children }) {
2090
2107
  ].includes(next)) return;
2091
2108
  setModeSetting(next);
2092
2109
  saveSettings(storageKey, {
2093
- ...loadSettings(storageKey),
2110
+ ...loadSettings(storageKey, settingDefaults),
2094
2111
  mode: next
2095
2112
  });
2096
- }, [storageKey]);
2113
+ }, [storageKey, settingDefaults]);
2097
2114
  useLayoutEffect(() => {
2098
2115
  if (base.custom) return;
2099
2116
  if (mode === "dark" && !isDarkTheme(base.tokens)) {
@@ -2135,6 +2152,8 @@ function ThemeProvider({ config = {}, children }) {
2135
2152
  }, [setMode]);
2136
2153
  const api = {
2137
2154
  state,
2155
+ /** The switcher's starting settings for this site. */
2156
+ settingDefaults,
2138
2157
  /** The mode in effect ('light' or 'dark'), the visitor's setting ('system' follows the device), and a setter. */
2139
2158
  mode,
2140
2159
  modeSetting,
@@ -5711,7 +5730,7 @@ function ThemeSwitcher() {
5711
5730
  return mount && createPortal(/* @__PURE__ */ jsx(Switcher, {}), mount);
5712
5731
  }
5713
5732
  function Switcher() {
5714
- const { issues, storageKey, tokens, position: requested, setLogoColouring, intro, mode, modeSetting, setMode } = useTheme();
5733
+ const { issues, storageKey, tokens, position: requested, setLogoColouring, intro, mode, modeSetting, setMode, settingDefaults } = useTheme();
5715
5734
  const position = CORNERS[requested] ? requested : "bottom-right";
5716
5735
  const [open, setOpen] = useState(false);
5717
5736
  const [arrived, setArrived] = useState(!intro);
@@ -5737,7 +5756,7 @@ function Switcher() {
5737
5756
  }, BURST_TIME);
5738
5757
  return () => clearTimeout(timer);
5739
5758
  }, [burst]);
5740
- const [settings, setSettings] = useState(() => loadSettings(storageKey));
5759
+ const [settings, setSettings] = useState(() => loadSettings(storageKey, settingDefaults));
5741
5760
  useEffect(() => setLogoColouring(settings.colourLogo), [settings.colourLogo, setLogoColouring]);
5742
5761
  const [auditOn, setAuditOn] = useState(false);
5743
5762
  const [findings, setFindings] = useState([]);
@@ -5913,7 +5932,7 @@ function Switcher() {
5913
5932
  const storeSettings = (next) => {
5914
5933
  saveSettings(storageKey, {
5915
5934
  ...next,
5916
- mode: loadSettings(storageKey).mode
5935
+ mode: loadSettings(storageKey, settingDefaults).mode
5917
5936
  });
5918
5937
  return next;
5919
5938
  };
@@ -5944,8 +5963,8 @@ function Switcher() {
5944
5963
  }));
5945
5964
  },
5946
5965
  reset: () => {
5947
- setMode(DEFAULT_SETTINGS.mode);
5948
- setSettings(storeSettings(DEFAULT_SETTINGS));
5966
+ setMode(settingDefaults.mode);
5967
+ setSettings(storeSettings(settingDefaults));
5949
5968
  },
5950
5969
  resetButton: saved ? resetPosition : null
5951
5970
  };
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-BlWfKjRg.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-BlWfKjRg.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.2",
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,16 @@ 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
98
+ /**
99
+ * The mode a first-time visitor starts in (default 'light'; 'system' follows their device). Any
100
+ * site can start dark: every theme has a dark twin. Visitors can still change it.
101
+ */
102
+ defaultMode?: 'light' | 'dark' | 'system'
93
103
  }
94
104
 
95
105
  export interface ThemeProviderProps {