colorsbymax 0.2.0 → 0.2.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.
@@ -1170,7 +1170,7 @@ function inferRoles(colors) {
1170
1170
  const readable = (c, min) => contrastRatio(c.hex, background) >= min;
1171
1171
  const ink = (byText.find((c) => readable(c, 4.5)) ?? byText[0])?.hex ?? "#1f2937";
1172
1172
  const inkSecondary = byText.find((c) => c.hex !== ink && deltaE(c.hex, ink) > 8 && readable(c, 3))?.hex;
1173
- const accents = colors.map((c) => {
1173
+ const accents = colors.filter((c) => c.hex !== ink && c.hex !== inkSecondary).map((c) => {
1174
1174
  const [h, s, l] = toHsl(c.hex);
1175
1175
  const share = c.bg / BG + c.text / TEXT + c.border / BORDER * .5;
1176
1176
  return {
@@ -1289,7 +1289,25 @@ function suggestThemes(scan, siteName, library = []) {
1289
1289
  //#region src/recolour.js
1290
1290
  var ATTR = "data-cbm";
1291
1291
  /** Elements the engine never touches: the switcher itself. */
1292
- var SKIP = "colorsbymax-root";
1292
+ var SKIP$1 = "colorsbymax-root";
1293
+ /**
1294
+ * How colorsbymax finds a site's logo, to keep it in its own colours: an explicit
1295
+ * data-colorsbymax-logo, "logo" in a class, id or label (but not "logout"), or common brand classes.
1296
+ */
1297
+ var LOGO_SELECTOR = [
1298
+ "[data-colorsbymax-logo]",
1299
+ "[class~=\"logo\" i]",
1300
+ "[class*=\"logo-\" i]:not([class*=\"logout\" i])",
1301
+ "[class*=\"-logo\" i]",
1302
+ "[class*=\"_logo\" i]",
1303
+ "[class*=\"Logo\"]:not([class*=\"Logout\"])",
1304
+ "[id*=\"logo\" i]:not([id*=\"logout\" i])",
1305
+ "[aria-label*=\"logo\" i]",
1306
+ ".brand",
1307
+ ".navbar-brand",
1308
+ ".site-title",
1309
+ ".site-brand"
1310
+ ].join(", ");
1293
1311
  /** Below this HSL saturation a colour counts as a grey and follows the background→text scale. */
1294
1312
  var NEUTRAL_SATURATION = .12;
1295
1313
  /** Colours within this many degrees of a brand hue are shades of it and follow the theme's version. */
@@ -1320,9 +1338,28 @@ var SIDES = [
1320
1338
  "bottom",
1321
1339
  "left"
1322
1340
  ];
1341
+ /**
1342
+ * Contrast a swapped pair aims for (WCAG AA): 4.5:1 for text, 3:1 for icons. Never more than the
1343
+ * site's own original pair had, so deliberately soft text and icons stay soft.
1344
+ */
1345
+ var MIN_TEXT_CONTRAST = 4.5;
1346
+ var MIN_ICON_CONTRAST = 3;
1347
+ /** Painted colours that sit on a background, so are checked against it. */
1348
+ var FOREGROUND = /* @__PURE__ */ new Set([
1349
+ "color",
1350
+ "fill",
1351
+ "stroke"
1352
+ ]);
1323
1353
  /** Whether a background-image has any colours in it (gradients), rather than just images. */
1324
1354
  var HAS_COLOUR = new RegExp(COLOR_IN_GRADIENT.source, "i");
1325
1355
  var clamp$1 = (n, lo = 0, hi = 1) => Math.min(hi, Math.max(lo, n));
1356
+ /** Whether an element has text of its own, rather than only icons or child elements. */
1357
+ var hasOwnText = (el) => [...el.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim());
1358
+ /**
1359
+ * Whether a background image covers the element, so it counts as what's behind the element's
1360
+ * content. Decorative strips (e.g. an animated underline sized "0% 2px") don't.
1361
+ */
1362
+ var fillsElement = (cs) => Boolean(cs.backgroundImage && cs.backgroundImage !== "none") && cs.backgroundSize.split(",").some((size) => /^(auto|cover|contain|100% 100%|auto auto|100%)$/.test(size.trim()));
1326
1363
  var hueDist = (a, b) => Math.min(Math.abs(a - b), 360 - Math.abs(a - b));
1327
1364
  /** How far hue `a` is from `b`, signed, in -180..180. */
1328
1365
  var signedHue = (a, b) => (a - b + 540) % 360 - 180;
@@ -1369,29 +1406,46 @@ function createRecolourer() {
1369
1406
  const site = themeFromRoles(inferRoles(collectColors()).roles);
1370
1407
  const siteBgL = hslOf$1(site.background)[2];
1371
1408
  const siteInkL = hslOf$1(site.ink)[2];
1372
- /** Each distinct painted value, keyed "prop|value". */
1409
+ /** Each distinct painted value, keyed "prop|value", plus the background under text and icons. */
1373
1410
  const entries = /* @__PURE__ */ new Map();
1374
1411
  let theme = null;
1375
- const idFor = (prop, value, kind) => {
1376
- const key = `${prop}|${value}`;
1412
+ const idFor = (prop, value, kind, backdrop = null, role = null) => {
1413
+ const key = `${prop}|${value}|${backdrop ?? ""}|${role ?? ""}`;
1377
1414
  let entry = entries.get(key);
1378
1415
  if (!entry) {
1379
1416
  entry = {
1380
1417
  id: `c${entries.size.toString(36)}`,
1381
1418
  prop,
1382
1419
  value,
1383
- kind
1420
+ kind,
1421
+ backdrop,
1422
+ role
1384
1423
  };
1385
1424
  entries.set(key, entry);
1386
- if (theme) sheet.textContent += rule(entry);
1425
+ if (theme) pendingRules += rule(entry);
1387
1426
  }
1388
1427
  return entry.id;
1389
1428
  };
1429
+ let backdrops = /* @__PURE__ */ new WeakMap();
1430
+ const backdropOf = (el) => {
1431
+ if (!el || el.nodeType !== 1) return "rgb(255, 255, 255)";
1432
+ if (backdrops.has(el)) return backdrops.get(el);
1433
+ const cs = getComputedStyle(el);
1434
+ const own = parse(cs.backgroundColor);
1435
+ let value;
1436
+ if (own && own.alpha >= .5) value = cs.backgroundColor;
1437
+ else if (fillsElement(cs) && HAS_COLOUR.test(cs.backgroundImage)) value = cs.backgroundImage.match(HAS_COLOUR)[0];
1438
+ else value = el === document.documentElement ? "rgb(255, 255, 255)" : backdropOf(el.parentElement);
1439
+ backdrops.set(el, value);
1440
+ return value;
1441
+ };
1390
1442
  const read = (el) => {
1391
1443
  const cs = getComputedStyle(el);
1392
1444
  const ids = [];
1445
+ const role = el instanceof SVGElement || !hasOwnText(el) ? "icon" : "text";
1393
1446
  const colour = (prop, css, kind) => {
1394
- if (parse(css)) ids.push(idFor(prop, css, kind));
1447
+ if (!parse(css)) return;
1448
+ ids.push(FOREGROUND.has(prop) ? idFor(prop, css, kind, backdropOf(el), prop === "color" ? role : "icon") : idFor(prop, css, kind));
1395
1449
  };
1396
1450
  colour("background-color", cs.backgroundColor, "bg");
1397
1451
  colour("color", cs.color, "text");
@@ -1408,23 +1462,33 @@ function createRecolourer() {
1408
1462
  return ids;
1409
1463
  };
1410
1464
  /** Tags `elements` (and optionally their descendants) with the colours they paint. */
1465
+ let pendingRules = "";
1411
1466
  const tag = (roots, deep) => {
1467
+ backdrops = /* @__PURE__ */ new WeakMap();
1412
1468
  withoutAppliedTheme(() => {
1413
1469
  for (const root of roots) {
1414
- if (!(root instanceof Element) || root.closest(SKIP)) continue;
1470
+ if (!(root instanceof Element) || root.closest(skip)) continue;
1415
1471
  const all = deep ? [root, ...root.querySelectorAll("*")] : [root];
1416
1472
  for (const el of all) {
1417
- if (el.localName === SKIP) continue;
1473
+ if (el.localName === SKIP$1) continue;
1474
+ if (el.closest(skip)) {
1475
+ el.removeAttribute(ATTR);
1476
+ continue;
1477
+ }
1418
1478
  const ids = read(el);
1419
1479
  if (ids.length) el.setAttribute(ATTR, ids.join(" "));
1420
1480
  else el.removeAttribute(ATTR);
1421
1481
  }
1422
1482
  }
1423
1483
  });
1484
+ if (pendingRules) {
1485
+ sheet.textContent += pendingRules;
1486
+ pendingRules = "";
1487
+ }
1424
1488
  };
1425
- const mapColour = (css, kind) => {
1489
+ const mapHex = (css, kind) => {
1426
1490
  const c = parse(css);
1427
- if (!c) return css;
1491
+ if (!c) return null;
1428
1492
  const [, s, l] = hslOf$1(c.hex);
1429
1493
  let hex;
1430
1494
  if (s < NEUTRAL_SATURATION) {
@@ -1462,14 +1526,65 @@ function createRecolourer() {
1462
1526
  clamp$1(lightness)
1463
1527
  ]));
1464
1528
  }
1465
- if (c.alpha >= 1) return hex;
1529
+ return {
1530
+ hex,
1531
+ alpha: c.alpha
1532
+ };
1533
+ };
1534
+ const format = ({ hex, alpha }) => {
1535
+ if (alpha >= 1) return hex;
1466
1536
  const [r, g, b] = hexToRgb(hex);
1467
- return `rgba(${r}, ${g}, ${b}, ${c.alpha})`;
1537
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
1538
+ };
1539
+ const mapColour = (css, kind) => {
1540
+ const mapped = mapHex(css, kind);
1541
+ return mapped ? format(mapped) : css;
1542
+ };
1543
+ const readable = (fg, bg, target, lighter) => {
1544
+ const ratio = (c) => contrastRatio(c, bg);
1545
+ if (ratio(fg) >= target - .05) return fg;
1546
+ const pool = [
1547
+ fg,
1548
+ theme.background,
1549
+ theme.surface,
1550
+ theme.ink,
1551
+ theme["on-primary"],
1552
+ theme["on-secondary"],
1553
+ "#ffffff",
1554
+ "#000000"
1555
+ ];
1556
+ const sameSide = pool.filter((c) => luminance(c) > luminance(bg) === lighter);
1557
+ const nearest = (list) => list.reduce((a, c) => deltaE(c, fg) < deltaE(a, fg) ? c : a);
1558
+ const strongest = (list) => list.reduce((a, c) => ratio(c) > ratio(a) ? c : a);
1559
+ const passing = sameSide.filter((c) => ratio(c) >= target);
1560
+ if (passing.length) return nearest(passing);
1561
+ if (sameSide.length && ratio(strongest(sameSide)) >= Math.min(target, MIN_ICON_CONTRAST)) return strongest(sameSide);
1562
+ const flipped = pool.filter((c) => ratio(c) >= target);
1563
+ return flipped.length ? nearest(flipped) : strongest(pool);
1468
1564
  };
1469
1565
  const rule = (entry) => {
1470
- const value = entry.prop === "background-image" ? entry.value.replace(COLOR_IN_GRADIENT, (stop) => mapColour(stop, entry.kind)) : mapColour(entry.value, entry.kind);
1566
+ let value;
1567
+ if (entry.prop === "background-image") value = entry.value.replace(COLOR_IN_GRADIENT, (stop) => mapColour(stop, entry.kind));
1568
+ else if (entry.backdrop) {
1569
+ const fg = mapHex(entry.value, entry.kind);
1570
+ const bg = mapHex(entry.backdrop, "bg");
1571
+ const original = {
1572
+ fg: parse(entry.value)?.hex,
1573
+ bg: parse(entry.backdrop)?.hex
1574
+ };
1575
+ if (fg && bg && original.fg && original.bg) {
1576
+ const aim = entry.role === "text" ? MIN_TEXT_CONTRAST : MIN_ICON_CONTRAST;
1577
+ const target = Math.min(aim, contrastRatio(original.fg, original.bg));
1578
+ const lighter = luminance(original.fg) > luminance(original.bg);
1579
+ value = format({
1580
+ ...fg,
1581
+ hex: readable(fg.hex, bg.hex, target, lighter)
1582
+ });
1583
+ } else value = mapColour(entry.value, entry.kind);
1584
+ } else value = mapColour(entry.value, entry.kind);
1471
1585
  return `[${ATTR}~="${entry.id}"]{${entry.prop}:${value}!important}`;
1472
1586
  };
1587
+ let skip = `${SKIP$1}, ${LOGO_SELECTOR}`;
1473
1588
  tag([document.body], true);
1474
1589
  const observer = new MutationObserver((records) => {
1475
1590
  const added = [];
@@ -1481,7 +1596,7 @@ function createRecolourer() {
1481
1596
  if (r.type === "attributes") changed.add(r.target);
1482
1597
  }
1483
1598
  if (added.length) tag(added, true);
1484
- if (changed.size) tag([...changed], false);
1599
+ if (changed.size) tag([...changed], true);
1485
1600
  });
1486
1601
  observer.observe(document.body, {
1487
1602
  subtree: true,
@@ -1496,6 +1611,13 @@ function createRecolourer() {
1496
1611
  theme = tokens;
1497
1612
  sheet.textContent = tokens ? [...entries.values()].map(rule).join("") : "";
1498
1613
  },
1614
+ /** Whether the logo is re-coloured with the rest (off keeps it in its own colours). */
1615
+ setLogoColouring(on) {
1616
+ const next = on ? SKIP$1 : `${SKIP$1}, ${LOGO_SELECTOR}`;
1617
+ if (next === skip) return;
1618
+ skip = next;
1619
+ tag([document.body], true);
1620
+ },
1499
1621
  stop() {
1500
1622
  observer.disconnect();
1501
1623
  sheet.remove();
@@ -1650,6 +1772,8 @@ var newId = () => `custom-${Date.now().toString(36)}-${Math.random().toString(36
1650
1772
  * Where each token is used on this site, shown in the editors
1651
1773
  * @property {boolean} [scrollbars] Colour the page's scrollbars from the theme (default true)
1652
1774
  * @property {() => Promise<any>} [pdf] Enables PDF uploads: pass `loadPdf` from 'colorsbymax/pdf'
1775
+ * @property {boolean} [hidden] Hide the colour button and panel; the theme still applies. Use
1776
+ * `hidden: import.meta.env.PROD` to keep it out of production once the colours are chosen.
1653
1777
  * @property {'bottom-right' | 'bottom-left' | 'top-left' | 'top-right'} [position] Where the colour
1654
1778
  * button starts (default 'bottom-right'). 'top-right' sits under a floating nav bar.
1655
1779
  * @property {boolean | 'auto'} [recolour] Re-colour a site that doesn't paint with the --color-*
@@ -1675,20 +1799,28 @@ function resolveSite(config, pageColours) {
1675
1799
  tokens: completeTokens(sanitizeTokens(t.tokens), defaultTheme.tokens)
1676
1800
  }));
1677
1801
  const fixes = fixAll(defaultTheme.tokens);
1802
+ const accessible = Object.keys(fixes).length ? [{
1803
+ id: "site-accessible",
1804
+ name: `${defaultTheme.name} (accessible)`,
1805
+ site: true,
1806
+ tokens: {
1807
+ ...defaultTheme.tokens,
1808
+ ...fixes
1809
+ }
1810
+ }] : [];
1811
+ const pageOriginal = pageColours && config.defaultTheme ? [{
1812
+ id: "site-original",
1813
+ name: `${siteName} original`,
1814
+ site: true,
1815
+ tokens: pageColours
1816
+ }] : [];
1678
1817
  return {
1679
1818
  siteName,
1680
1819
  defaultTheme,
1681
1820
  siteThemes: [
1682
1821
  defaultTheme,
1683
- ...Object.keys(fixes).length ? [{
1684
- id: "site-accessible",
1685
- name: `${defaultTheme.name} (accessible)`,
1686
- site: true,
1687
- tokens: {
1688
- ...defaultTheme.tokens,
1689
- ...fixes
1690
- }
1691
- }] : [],
1822
+ ...accessible,
1823
+ ...pageOriginal,
1692
1824
  ...extra
1693
1825
  ]
1694
1826
  };
@@ -1739,7 +1871,23 @@ function ThemeProvider({ config = {}, children }) {
1739
1871
  recolourer.current = null;
1740
1872
  };
1741
1873
  }, [recolourMode]);
1742
- const original = base.id === defaultTheme.id && !Object.keys(state.overrides).length;
1874
+ const [logoColouring, setLogoColouring] = useState(false);
1875
+ useLayoutEffect(() => {
1876
+ recolourer.current?.setLogoColouring(logoColouring);
1877
+ }, [logoColouring, pageColours]);
1878
+ useLayoutEffect(() => {
1879
+ if (logoColouring || pageColours) return;
1880
+ const style = document.createElement("style");
1881
+ style.dataset.colorsbymax = "logo";
1882
+ style.textContent = `:is(${LOGO_SELECTOR}){${TOKEN_KEYS.map((k) => `--color-${k}:${defaultTheme.tokens[k]}`).join(";")}}`;
1883
+ document.head.append(style);
1884
+ return () => style.remove();
1885
+ }, [
1886
+ logoColouring,
1887
+ pageColours,
1888
+ defaultTheme
1889
+ ]);
1890
+ const original = (base.id === "site-original" || base.id === defaultTheme.id && !initialConfig.defaultTheme) && !Object.keys(state.overrides).length;
1743
1891
  useLayoutEffect(() => {
1744
1892
  recolourer.current?.apply(original ? null : tokens);
1745
1893
  }, [
@@ -1788,8 +1936,12 @@ function ThemeProvider({ config = {}, children }) {
1788
1936
  usage: initialConfig.usage ?? {},
1789
1937
  loadPdf: initialConfig.pdf ?? null,
1790
1938
  position: initialConfig.position ?? "bottom-right",
1939
+ /** True when the config hides the switcher (e.g. in production); the theme still applies. */
1940
+ hidden: Boolean(initialConfig.hidden),
1791
1941
  /** True when colorsbymax is swapping the page's own colours (the site isn't wired to tokens). */
1792
1942
  recolouring: Boolean(pageColours),
1943
+ /** Turns re-colouring of the site's logo on or off (the switcher's "Colour the logo" setting). */
1944
+ setLogoColouring,
1793
1945
  siteThemes,
1794
1946
  defaultTheme,
1795
1947
  presets: PRESETS,
@@ -2131,6 +2283,23 @@ var Wand2 = icon("wand-sparkles", [
2131
2283
  ["path", { "d": "M11 3H9" }]
2132
2284
  ]);
2133
2285
  var X = icon("x", [["path", { "d": "M18 6 6 18" }], ["path", { "d": "m6 6 12 12" }]]);
2286
+ var ScanSearch = icon("scan-search", [
2287
+ ["path", { "d": "M3 7V5a2 2 0 0 1 2-2h2" }],
2288
+ ["path", { "d": "M17 3h2a2 2 0 0 1 2 2v2" }],
2289
+ ["path", { "d": "M21 17v2a2 2 0 0 1-2 2h-2" }],
2290
+ ["path", { "d": "M7 21H5a2 2 0 0 1-2-2v-2" }],
2291
+ ["circle", {
2292
+ "cx": "12",
2293
+ "cy": "12",
2294
+ "r": "3"
2295
+ }],
2296
+ ["path", { "d": "m16 16-1.9-1.9" }]
2297
+ ]);
2298
+ var Contrast = icon("contrast", [["circle", {
2299
+ "cx": "12",
2300
+ "cy": "12",
2301
+ "r": "10"
2302
+ }], ["path", { "d": "M12 18a6 6 0 0 0 0-12v12z" }]]);
2134
2303
  /** Images are scaled down to at most this many pixels on their longest side before sampling. */
2135
2304
  var SAMPLE_SIZE = 200;
2136
2305
  /** PDF pages read, from the first. */
@@ -2332,6 +2501,26 @@ function themeFromPalette(colours) {
2332
2501
  };
2333
2502
  }
2334
2503
  //#endregion
2504
+ //#region src/finish.js
2505
+ /** The production check for Vite projects; other bundlers use NODE_ENV. */
2506
+ var VITE_PROD = "import.meta.env.PROD";
2507
+ var NODE_PROD = "process.env.NODE_ENV === 'production'";
2508
+ var tokenLines = (tokens, indent) => TOKEN_KEYS.map((k) => `${indent}'${k}': '${tokens[k]}',`).join("\n");
2509
+ /**
2510
+ * Code that makes the chosen colours the site's default and hides the switcher in production.
2511
+ * @param {'auto' | 'provider'} kind the one-line setup (autoMount) or ThemeProvider
2512
+ */
2513
+ function keepSnippet(kind, name, tokens, prod = VITE_PROD) {
2514
+ const theme = `defaultTheme: {\n name: ${JSON.stringify(name)},\n tokens: {\n${tokenLines(tokens, " ")}\n },\n },\n // Hides the colour button in production; set to false to bring it back.\n hidden: ${prod},`;
2515
+ if (kind === "auto") return `// Replace \`import 'colorsbymax/auto'\` with:\nimport { autoMount } from 'colorsbymax/auto'\n\nautoMount({\n ${theme}\n})`;
2516
+ return `// Add to the config you pass to <ThemeProvider>:\n<ThemeProvider config={{\n ...config,\n ${theme}\n}}>`;
2517
+ }
2518
+ /** The theme as `--color-*` variables on :root. */
2519
+ var cssSnippet = (tokens) => `:root {\n${TOKEN_KEYS.map((k) => ` --color-${k}: ${tokens[k]};`).join("\n")}\n}`;
2520
+ var keepPrompt = (name, tokens) => `Update my colorsbymax setup so the colours I chose become my site's default and the colour switcher is hidden in production. In the colorsbymax config (the autoMount({...}) call, or the config passed to <ThemeProvider>), set defaultTheme to { name: ${JSON.stringify(name)}, tokens: ${JSON.stringify(tokens)} } and set hidden: ${VITE_PROD} (use ${NODE_PROD} if this isn't a Vite project). If the site uses import 'colorsbymax/auto', replace it with import { autoMount } from 'colorsbymax/auto' and an autoMount({...}) call with that config. Don't change anything else.`;
2521
+ /** @param {boolean} recolouring true when colorsbymax was swapping the site's hard-coded colours */
2522
+ var removePrompt = (tokens, recolouring) => recolouring ? `Remove colorsbymax from this project but keep the colours it currently shows. The site's CSS uses hard-coded colours that colorsbymax was swapping at runtime, so update the site's own CSS to use this palette instead (primary is the main brand colour, background the page, ink the text): ${JSON.stringify(tokens)}. Then uninstall the colorsbymax package and delete its import (import 'colorsbymax/auto', autoMount, ThemeProvider or ThemeSwitcher) and any colorsbymax pre-paint script in index.html.` : `Remove colorsbymax from this project but keep my colours: add these CSS variables to my global stylesheet, replacing any existing --color-* values: ${cssSnippet(tokens)} Then uninstall the colorsbymax package and delete its usage (ThemeProvider, ThemeSwitcher, autoMount or import 'colorsbymax/auto') and any colorsbymax pre-paint script in index.html. Keep colorsbymax/tokens.css only if nothing else needs it.`;
2523
+ //#endregion
2335
2524
  //#region src/settings.js
2336
2525
  /**
2337
2526
  * @typedef {Object} PanelSettings
@@ -2346,6 +2535,8 @@ function themeFromPalette(colours) {
2346
2535
  * @property {number | null} panelWidth Pixels; null is the standard width
2347
2536
  * @property {number | 'full' | null} panelHeight Pixels, 'full' for all the room there is, or null to fit the content
2348
2537
  * @property {boolean} libraryCollapsed Whether the library's category chips are folded away
2538
+ * @property {boolean} colourLogo Whether themes re-colour the site's logo too (off keeps its own colours)
2539
+ * @property {boolean} hideButton Hidden on this device from the finish screen (Alt+Shift+C brings it back)
2349
2540
  */
2350
2541
  /** @type {PanelSettings} */
2351
2542
  var DEFAULT_SETTINGS = {
@@ -2359,7 +2550,9 @@ var DEFAULT_SETTINGS = {
2359
2550
  animateDot: true,
2360
2551
  panelWidth: null,
2361
2552
  panelHeight: null,
2362
- libraryCollapsed: false
2553
+ libraryCollapsed: false,
2554
+ colourLogo: false,
2555
+ hideButton: false
2363
2556
  };
2364
2557
  /** Size presets offered in settings; dragging an edge gives a custom size instead. */
2365
2558
  var PANEL_PRESETS = [
@@ -2483,7 +2676,7 @@ function useSavedToYours() {
2483
2676
  }
2484
2677
  function ThemePanel() {
2485
2678
  const theme = useTheme();
2486
- const { settings, mode } = useSettings();
2679
+ const { settings, mode, audit } = useSettings();
2487
2680
  const overrideCount = Object.keys(theme.state.overrides).length;
2488
2681
  const [view, setView] = useState("main");
2489
2682
  const returnFocus = useRef(null);
@@ -2546,21 +2739,35 @@ function ThemePanel() {
2546
2739
  }), /* @__PURE__ */ jsx("p", {
2547
2740
  className: "text-[11px] text-zinc-500",
2548
2741
  children: "by mrmaxdesigns"
2549
- })] }), /* @__PURE__ */ jsx("button", {
2550
- type: "button",
2551
- onClick: (e) => view === "settings" ? setView("main") : showView("settings", e.currentTarget),
2552
- className: `${btn} px-1.5 ${view === "settings" ? "border-zinc-900 bg-zinc-100" : "border-transparent"}`,
2553
- "aria-label": "Panel settings",
2554
- "aria-pressed": view === "settings",
2555
- "data-tip": "Panel settings",
2556
- children: /* @__PURE__ */ jsx(Settings, {
2557
- className: "w-4 h-4",
2558
- "aria-hidden": "true"
2559
- })
2742
+ })] }), /* @__PURE__ */ jsxs("div", {
2743
+ className: "flex items-center gap-1",
2744
+ children: [/* @__PURE__ */ jsxs("button", {
2745
+ type: "button",
2746
+ onClick: audit.toggle,
2747
+ className: `${btn} px-2 ${audit.on ? "border-zinc-900 bg-zinc-100" : "border-transparent"}`,
2748
+ "aria-pressed": audit.on,
2749
+ "data-tip": "Audit the page: point out what won’t look right with these colours",
2750
+ children: [/* @__PURE__ */ jsx(ScanSearch, {
2751
+ className: "w-4 h-4",
2752
+ "aria-hidden": "true"
2753
+ }), "Audit"]
2754
+ }), /* @__PURE__ */ jsx("button", {
2755
+ type: "button",
2756
+ onClick: (e) => view === "settings" ? setView("main") : showView("settings", e.currentTarget),
2757
+ className: `${btn} px-1.5 ${view === "settings" ? "border-zinc-900 bg-zinc-100" : "border-transparent"}`,
2758
+ "aria-label": "Panel settings",
2759
+ "aria-pressed": view === "settings",
2760
+ "data-tip": "Panel settings",
2761
+ children: /* @__PURE__ */ jsx(Settings, {
2762
+ className: "w-4 h-4",
2763
+ "aria-hidden": "true"
2764
+ })
2765
+ })]
2560
2766
  })]
2561
2767
  }),
2562
2768
  view === "contrast" && /* @__PURE__ */ jsx(ContrastView, { onBack: () => setView("main") }),
2563
2769
  view === "settings" && /* @__PURE__ */ jsx(SettingsView, { onBack: () => setView("main") }),
2770
+ view === "finish" && /* @__PURE__ */ jsx(FinishView, { onBack: () => setView("main") }),
2564
2771
  /* @__PURE__ */ jsxs("div", {
2565
2772
  hidden: view !== "main",
2566
2773
  children: [/* @__PURE__ */ jsxs(ContrastNav.Provider, {
@@ -2587,14 +2794,25 @@ function ThemePanel() {
2587
2794
  ]
2588
2795
  }), /* @__PURE__ */ jsx("footer", {
2589
2796
  className: "border-t border-zinc-200 px-4 py-3",
2590
- children: /* @__PURE__ */ jsxs("button", {
2591
- type: "button",
2592
- className: `${btn} w-full`,
2593
- onClick: resetToDefault,
2594
- children: [/* @__PURE__ */ jsx(RotateCcw, {
2595
- className: "w-3.5 h-3.5",
2596
- "aria-hidden": "true"
2597
- }), "Reset to default"]
2797
+ children: /* @__PURE__ */ jsxs("div", {
2798
+ className: "flex gap-2",
2799
+ children: [/* @__PURE__ */ jsxs("button", {
2800
+ type: "button",
2801
+ className: `${btn} flex-1`,
2802
+ onClick: resetToDefault,
2803
+ children: [/* @__PURE__ */ jsx(RotateCcw, {
2804
+ className: "w-3.5 h-3.5",
2805
+ "aria-hidden": "true"
2806
+ }), "Reset to default"]
2807
+ }), /* @__PURE__ */ jsxs("button", {
2808
+ type: "button",
2809
+ className: `${btnPrimary} flex-1`,
2810
+ onClick: (e) => showView("finish", e.currentTarget),
2811
+ children: [/* @__PURE__ */ jsx(Check, {
2812
+ className: "w-3.5 h-3.5",
2813
+ "aria-hidden": "true"
2814
+ }), "I’m done"]
2815
+ })]
2598
2816
  })
2599
2817
  })]
2600
2818
  })
@@ -2664,6 +2882,296 @@ function Toast({ toast, onDismiss }) {
2664
2882
  ]
2665
2883
  });
2666
2884
  }
2885
+ /** Shows code with a copy button. */
2886
+ function CopyBlock({ label, text, copyLabel = "Copy" }) {
2887
+ const { toast } = usePanel();
2888
+ const id = useId();
2889
+ return /* @__PURE__ */ jsxs("div", {
2890
+ className: "space-y-1",
2891
+ children: [/* @__PURE__ */ jsxs("div", {
2892
+ className: "flex items-center justify-between gap-2",
2893
+ children: [/* @__PURE__ */ jsx("span", {
2894
+ id,
2895
+ className: "text-[11px] font-semibold text-zinc-700",
2896
+ children: label
2897
+ }), /* @__PURE__ */ jsxs("button", {
2898
+ type: "button",
2899
+ className: `${btn} px-2 py-1`,
2900
+ onClick: async () => {
2901
+ try {
2902
+ await navigator.clipboard.writeText(text);
2903
+ toast(`Copied: ${label.toLowerCase()}.`);
2904
+ } catch {
2905
+ toast("Couldn’t reach the clipboard. Select the text and copy it instead.");
2906
+ }
2907
+ },
2908
+ children: [
2909
+ /* @__PURE__ */ jsx(Copy, {
2910
+ className: "w-3.5 h-3.5",
2911
+ "aria-hidden": "true"
2912
+ }),
2913
+ " ",
2914
+ copyLabel
2915
+ ]
2916
+ })]
2917
+ }), /* @__PURE__ */ jsx("pre", {
2918
+ "aria-labelledby": id,
2919
+ tabIndex: 0,
2920
+ className: "max-h-40 overflow-auto rounded-lg border border-zinc-200 bg-zinc-50 p-2.5 font-mono text-[11px] leading-snug text-zinc-800 whitespace-pre-wrap break-all",
2921
+ children: text
2922
+ })]
2923
+ });
2924
+ }
2925
+ var FINISH_OPTIONS = [
2926
+ {
2927
+ id: "keep",
2928
+ label: "Keep these colours and hide it in production",
2929
+ note: "Recommended. Everyone sees your colours; the button still shows while you develop."
2930
+ },
2931
+ {
2932
+ id: "local",
2933
+ label: "Hide it on this device only",
2934
+ note: "Quick and undoable. Nothing changes for anyone else."
2935
+ },
2936
+ {
2937
+ id: "remove",
2938
+ label: "Remove colorsbymax",
2939
+ note: "Uninstall it and keep the colours in your own CSS."
2940
+ }
2941
+ ];
2942
+ /**
2943
+ * After "I'm done": how to keep the chosen colours for every visitor and hide the switcher, hide it
2944
+ * here only, or remove colorsbymax, each with code and a prompt for an AI editor. Cancel goes back.
2945
+ */
2946
+ function FinishView({ onBack }) {
2947
+ const { tokens, active, recolouring } = useTheme();
2948
+ const { update } = useSettings();
2949
+ const [choice, setChoice] = useState("keep");
2950
+ const [kind, setKind] = useState(() => document.querySelector("[data-colorsbymax=\"auto\"]") ? "auto" : "provider");
2951
+ const headingRef = useRef(null);
2952
+ useEffect(() => headingRef.current?.focus(), []);
2953
+ const name = active.name.replace(/ \(dark\)$/, " dark");
2954
+ return /* @__PURE__ */ jsxs("div", {
2955
+ className: "px-4 py-3 space-y-4",
2956
+ children: [
2957
+ /* @__PURE__ */ jsxs("button", {
2958
+ type: "button",
2959
+ className: `${btn} border-transparent px-1.5`,
2960
+ onClick: onBack,
2961
+ children: [/* @__PURE__ */ jsx(ArrowLeft, {
2962
+ className: "w-3.5 h-3.5",
2963
+ "aria-hidden": "true"
2964
+ }), " Back"]
2965
+ }),
2966
+ /* @__PURE__ */ jsxs("div", {
2967
+ className: "space-y-1.5",
2968
+ children: [
2969
+ /* @__PURE__ */ jsx("h3", {
2970
+ ref: headingRef,
2971
+ tabIndex: -1,
2972
+ className: "text-sm font-semibold focus:outline-none",
2973
+ children: "Happy with your colours?"
2974
+ }),
2975
+ /* @__PURE__ */ jsxs("p", {
2976
+ className: "text-[11px] text-zinc-600",
2977
+ children: [
2978
+ "Your pick, ",
2979
+ /* @__PURE__ */ jsx("strong", {
2980
+ className: "font-semibold text-zinc-900",
2981
+ children: active.name
2982
+ }),
2983
+ ", is only saved in this browser. To show it to every visitor and keep the button out of production, put it in your code:"
2984
+ ]
2985
+ }),
2986
+ /* @__PURE__ */ jsx(Swatches, { tokens })
2987
+ ]
2988
+ }),
2989
+ /* @__PURE__ */ jsx("div", {
2990
+ role: "radiogroup",
2991
+ "aria-label": "What to do",
2992
+ className: "space-y-1.5",
2993
+ children: FINISH_OPTIONS.map((o) => /* @__PURE__ */ jsxs("button", {
2994
+ type: "button",
2995
+ role: "radio",
2996
+ "aria-checked": choice === o.id,
2997
+ onClick: () => setChoice(o.id),
2998
+ className: `w-full rounded-xl border px-3 py-2 text-left cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-900 ${choice === o.id ? "border-zinc-900 bg-zinc-100" : "border-zinc-200 hover:bg-zinc-50"}`,
2999
+ children: [/* @__PURE__ */ jsx("span", {
3000
+ className: "block text-xs font-semibold text-zinc-900",
3001
+ children: o.label
3002
+ }), /* @__PURE__ */ jsx("span", {
3003
+ className: "block text-[11px] text-zinc-600",
3004
+ children: o.note
3005
+ })]
3006
+ }, o.id))
3007
+ }),
3008
+ choice === "keep" && /* @__PURE__ */ jsxs("div", {
3009
+ className: "space-y-3",
3010
+ children: [
3011
+ /* @__PURE__ */ jsx("div", {
3012
+ role: "radiogroup",
3013
+ "aria-label": "How colorsbymax is set up",
3014
+ className: "grid grid-cols-2 gap-2",
3015
+ children: [["auto", "One-line import"], ["provider", "ThemeProvider"]].map(([id, label]) => /* @__PURE__ */ jsx("button", {
3016
+ type: "button",
3017
+ role: "radio",
3018
+ "aria-checked": kind === id,
3019
+ onClick: () => setKind(id),
3020
+ className: `h-8 rounded-lg border px-2 text-xs font-medium cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-900 ${kind === id ? "border-zinc-900 bg-zinc-900 text-white" : "border-zinc-300 bg-white text-zinc-800 hover:bg-zinc-50"}`,
3021
+ children: label
3022
+ }, id))
3023
+ }),
3024
+ /* @__PURE__ */ jsx(CopyBlock, {
3025
+ label: "Code for your site",
3026
+ text: keepSnippet(kind, name, tokens)
3027
+ }),
3028
+ /* @__PURE__ */ jsxs("p", {
3029
+ className: "text-[11px] text-zinc-600",
3030
+ children: [
3031
+ "Not using Vite? Use ",
3032
+ /* @__PURE__ */ jsx("code", {
3033
+ className: "font-mono",
3034
+ children: "process.env.NODE_ENV === 'production'"
3035
+ }),
3036
+ " instead of ",
3037
+ /* @__PURE__ */ jsx("code", {
3038
+ className: "font-mono",
3039
+ children: "import.meta.env.PROD"
3040
+ }),
3041
+ " (Next.js, webpack)."
3042
+ ]
3043
+ }),
3044
+ /* @__PURE__ */ jsx(CopyBlock, {
3045
+ label: "Or ask Claude, Cursor or Copilot",
3046
+ text: keepPrompt(name, tokens),
3047
+ copyLabel: "Copy prompt"
3048
+ }),
3049
+ /* @__PURE__ */ jsxs("div", {
3050
+ className: "rounded-lg bg-zinc-100 p-2.5 text-[11px] text-zinc-700 space-y-1.5",
3051
+ children: [/* @__PURE__ */ jsxs("p", { children: [
3052
+ /* @__PURE__ */ jsx("strong", {
3053
+ className: "font-semibold text-zinc-900",
3054
+ children: "Bring it back later:"
3055
+ }),
3056
+ " it still shows when you run the site locally, so you can keep iterating. To show it in production too, set ",
3057
+ /* @__PURE__ */ jsx("code", {
3058
+ className: "font-mono",
3059
+ children: "hidden: false"
3060
+ }),
3061
+ "."
3062
+ ] }), /* @__PURE__ */ jsx(CopyBlock, {
3063
+ label: "Prompt to bring it back",
3064
+ text: "Show the colorsbymax colour switcher again in production: in its config (the autoMount({...}) call or the config passed to <ThemeProvider>), set hidden to false or remove the hidden line. Don't change anything else.",
3065
+ copyLabel: "Copy prompt"
3066
+ })]
3067
+ })
3068
+ ]
3069
+ }),
3070
+ choice === "local" && /* @__PURE__ */ jsxs("div", {
3071
+ className: "space-y-2",
3072
+ children: [
3073
+ /* @__PURE__ */ jsx("p", {
3074
+ className: "text-[11px] text-zinc-600",
3075
+ children: "Hides the colour button in this browser only, and keeps showing your current colours here. It doesn’t change your code or what anyone else sees."
3076
+ }),
3077
+ /* @__PURE__ */ jsxs("p", {
3078
+ className: "rounded-lg bg-zinc-100 p-2.5 text-[11px] text-zinc-700",
3079
+ children: [
3080
+ /* @__PURE__ */ jsx("strong", {
3081
+ className: "font-semibold text-zinc-900",
3082
+ children: "To bring it back:"
3083
+ }),
3084
+ " press ",
3085
+ /* @__PURE__ */ jsx("kbd", {
3086
+ className: "font-mono",
3087
+ children: "Alt"
3088
+ }),
3089
+ "+",
3090
+ /* @__PURE__ */ jsx("kbd", {
3091
+ className: "font-mono",
3092
+ children: "Shift"
3093
+ }),
3094
+ "+",
3095
+ /* @__PURE__ */ jsx("kbd", {
3096
+ className: "font-mono",
3097
+ children: "C"
3098
+ }),
3099
+ " on the page, or open it with ",
3100
+ /* @__PURE__ */ jsx("code", {
3101
+ className: "font-mono",
3102
+ children: "?colorsbymax"
3103
+ }),
3104
+ " at the end of the address."
3105
+ ]
3106
+ }),
3107
+ /* @__PURE__ */ jsx("button", {
3108
+ type: "button",
3109
+ className: `${btnPrimary} w-full`,
3110
+ onClick: () => update({ hideButton: true }),
3111
+ children: "Hide the button here"
3112
+ })
3113
+ ]
3114
+ }),
3115
+ choice === "remove" && /* @__PURE__ */ jsxs("div", {
3116
+ className: "space-y-3",
3117
+ children: [
3118
+ /* @__PURE__ */ jsxs("ol", {
3119
+ className: "list-decimal space-y-1 pl-4 text-[11px] text-zinc-700",
3120
+ children: [
3121
+ /* @__PURE__ */ jsxs("li", { children: ["Uninstall it: ", /* @__PURE__ */ jsx("code", {
3122
+ className: "font-mono",
3123
+ children: "npm uninstall colorsbymax"
3124
+ })] }),
3125
+ /* @__PURE__ */ jsxs("li", { children: [
3126
+ "Delete its import (",
3127
+ /* @__PURE__ */ jsx("code", {
3128
+ className: "font-mono",
3129
+ children: "import 'colorsbymax/auto'"
3130
+ }),
3131
+ ", ",
3132
+ /* @__PURE__ */ jsx("code", {
3133
+ className: "font-mono",
3134
+ children: "autoMount"
3135
+ }),
3136
+ ", or ",
3137
+ /* @__PURE__ */ jsx("code", {
3138
+ className: "font-mono",
3139
+ children: "ThemeProvider"
3140
+ }),
3141
+ " and ",
3142
+ /* @__PURE__ */ jsx("code", {
3143
+ className: "font-mono",
3144
+ children: "ThemeSwitcher"
3145
+ }),
3146
+ ") and any colorsbymax pre-paint script."
3147
+ ] }),
3148
+ /* @__PURE__ */ jsx("li", { children: recolouring ? "Keep the colours: your site’s CSS uses its own hard-coded colours, which colorsbymax was swapping as the page ran, so they need writing into your CSS. The prompt below asks your AI editor to do that." : "Keep the colours: paste these into your global stylesheet, replacing your current --color-* values." })
3149
+ ]
3150
+ }),
3151
+ !recolouring && /* @__PURE__ */ jsx(CopyBlock, {
3152
+ label: "CSS for your colours",
3153
+ text: cssSnippet(tokens)
3154
+ }),
3155
+ /* @__PURE__ */ jsx(CopyBlock, {
3156
+ label: "Or ask Claude, Cursor or Copilot",
3157
+ text: removePrompt(tokens, recolouring),
3158
+ copyLabel: "Copy prompt"
3159
+ }),
3160
+ /* @__PURE__ */ jsx("p", {
3161
+ className: "text-[11px] text-zinc-600",
3162
+ children: "To bring it back later, install it again and follow the Quick start in the README."
3163
+ })
3164
+ ]
3165
+ }),
3166
+ /* @__PURE__ */ jsx("button", {
3167
+ type: "button",
3168
+ className: `${btn} w-full`,
3169
+ onClick: onBack,
3170
+ children: "Cancel, keep using colorsbymax"
3171
+ })
3172
+ ]
3173
+ });
3174
+ }
2667
3175
  var MODES = [
2668
3176
  {
2669
3177
  id: "light",
@@ -2811,6 +3319,18 @@ function SettingsView({ onBack }) {
2811
3319
  })
2812
3320
  ]
2813
3321
  }),
3322
+ /* @__PURE__ */ jsxs("fieldset", {
3323
+ className: "space-y-1",
3324
+ children: [/* @__PURE__ */ jsx("legend", {
3325
+ className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-zinc-600",
3326
+ children: "Page"
3327
+ }), /* @__PURE__ */ jsx(Toggle, {
3328
+ checked: settings.colourLogo,
3329
+ onChange: (v) => update({ colourLogo: v }),
3330
+ label: "Colour the logo too",
3331
+ note: "Off keeps the site’s logo in its own colours whatever the theme"
3332
+ })]
3333
+ }),
2814
3334
  /* @__PURE__ */ jsxs("fieldset", {
2815
3335
  className: "space-y-1",
2816
3336
  children: [
@@ -4070,8 +4590,509 @@ function ImportExport() {
4070
4590
  });
4071
4591
  }
4072
4592
  //#endregion
4593
+ //#region src/audit.js
4594
+ /** Elements read, at most, so audits stay quick on huge pages. */
4595
+ var MAX_ELEMENTS = 3e3;
4596
+ var SKIP = "colorsbymax-root";
4597
+ var parser = () => {
4598
+ const ctx = Object.assign(document.createElement("canvas"), {
4599
+ width: 1,
4600
+ height: 1
4601
+ }).getContext("2d", { willReadFrequently: true });
4602
+ return (css) => {
4603
+ if (!css || css === "none" || css === "transparent") return null;
4604
+ ctx.clearRect(0, 0, 1, 1);
4605
+ ctx.fillStyle = "#000";
4606
+ ctx.fillStyle = css;
4607
+ ctx.fillRect(0, 0, 1, 1);
4608
+ const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
4609
+ return a < 128 ? null : rgbToHex([
4610
+ r,
4611
+ g,
4612
+ b
4613
+ ]);
4614
+ };
4615
+ };
4616
+ var visible = (el) => {
4617
+ if (el.checkVisibility && !el.checkVisibility({
4618
+ opacityProperty: true,
4619
+ visibilityProperty: true
4620
+ })) return false;
4621
+ const r = el.getBoundingClientRect();
4622
+ return r.width > 2 && r.height > 2;
4623
+ };
4624
+ var ownText = (el) => [...el.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim());
4625
+ /** A short quote of an element's text for its note, cut at a word. */
4626
+ var describe = (el) => {
4627
+ const text = (el.getAttribute("aria-label") || el.alt || el.textContent || "").trim().replace(/\s+/g, " ");
4628
+ if (text.length <= 48) return text;
4629
+ const cut = text.slice(0, 48);
4630
+ const space = cut.lastIndexOf(" ");
4631
+ return `${(space > 20 ? cut.slice(0, space) : cut).replace(/[,.;:]$/, "")}…`;
4632
+ };
4633
+ var fmt = (r) => `${(Math.floor(r * 10) / 10).toFixed(1)}:1`;
4634
+ /**
4635
+ * Samples an image's corners and middle (for a solid background and an overall colour). Returns
4636
+ * null when the browser won't allow reading it (a cross-origin image without CORS).
4637
+ */
4638
+ function sampleImage(img) {
4639
+ try {
4640
+ const w = 24;
4641
+ const h = Math.max(1, Math.round(img.naturalHeight / Math.max(1, img.naturalWidth) * w));
4642
+ const ctx = Object.assign(document.createElement("canvas"), {
4643
+ width: w,
4644
+ height: h
4645
+ }).getContext("2d", { willReadFrequently: true });
4646
+ ctx.drawImage(img, 0, 0, w, h);
4647
+ const data = ctx.getImageData(0, 0, w, h).data;
4648
+ const at = (x, y) => {
4649
+ const i = (y * w + x) * 4;
4650
+ return data[i + 3] < 200 ? null : rgbToHex([
4651
+ data[i],
4652
+ data[i + 1],
4653
+ data[i + 2]
4654
+ ]);
4655
+ };
4656
+ const corners = [
4657
+ at(0, 0),
4658
+ at(23, 0),
4659
+ at(0, h - 1),
4660
+ at(23, h - 1)
4661
+ ];
4662
+ let r = 0, g = 0, b = 0, n = 0;
4663
+ for (let i = 0; i < data.length; i += 4) {
4664
+ if (data[i + 3] < 128) continue;
4665
+ r += data[i];
4666
+ g += data[i + 1];
4667
+ b += data[i + 2];
4668
+ n++;
4669
+ }
4670
+ return {
4671
+ corners,
4672
+ average: n ? rgbToHex([
4673
+ r / n,
4674
+ g / n,
4675
+ b / n
4676
+ ].map(Math.round)) : null,
4677
+ transparent: n < data.length / 4 * .9
4678
+ };
4679
+ } catch {
4680
+ return null;
4681
+ }
4682
+ }
4683
+ /**
4684
+ * Audits the page as painted now.
4685
+ * @param {{ logoColouring: boolean }} options
4686
+ * @returns {{ id: string, el: Element, title: string, message: string, action?: 'colour-logo' | 'invert' }[]}
4687
+ */
4688
+ function runAudit({ logoColouring }) {
4689
+ const parse = parser();
4690
+ const findings = [];
4691
+ const flagged = /* @__PURE__ */ new Set();
4692
+ const add = (el, finding) => {
4693
+ for (let n = el; n; n = n.parentElement) if (flagged.has(n)) return;
4694
+ flagged.add(el);
4695
+ findings.push({
4696
+ id: `${finding.kind}-${findings.length}`,
4697
+ el,
4698
+ ...finding
4699
+ });
4700
+ };
4701
+ const cache = /* @__PURE__ */ new WeakMap();
4702
+ const behind = (el) => {
4703
+ if (!el || el.nodeType !== 1) return parse(getComputedStyle(document.documentElement).backgroundColor) ?? "#ffffff";
4704
+ if (cache.has(el)) return cache.get(el);
4705
+ const cs = getComputedStyle(el);
4706
+ let out;
4707
+ const own = parse(cs.backgroundColor);
4708
+ if (own) out = own;
4709
+ else if (/url\(/.test(cs.backgroundImage)) out = null;
4710
+ else out = el === document.body ? parse(getComputedStyle(document.documentElement).backgroundColor) ?? "#ffffff" : behind(el.parentElement);
4711
+ cache.set(el, out);
4712
+ return out;
4713
+ };
4714
+ const pageBg = behind(document.body) ?? "#ffffff";
4715
+ const darkPage = luminance(pageBg) < .2;
4716
+ for (const logo of document.querySelectorAll(LOGO_SELECTOR)) {
4717
+ if (logo.closest(SKIP) || !visible(logo)) continue;
4718
+ if (logo.parentElement?.closest(LOGO_SELECTOR)) continue;
4719
+ const bg = behind(logo.parentElement) ?? pageBg;
4720
+ const img = logo.localName === "img" ? logo : logo.querySelector("img");
4721
+ if (img && img.complete && img.naturalWidth) {
4722
+ const sample = sampleImage(img);
4723
+ const ratio = sample?.average ? contrastRatio(sample.average, bg) : null;
4724
+ if (ratio !== null && ratio < 3) add(img, {
4725
+ kind: "logo-image",
4726
+ title: "Logo is hard to see here",
4727
+ message: `Your logo is a picture, which colorsbymax can’t re-colour, and it reads at only ${fmt(ratio)} on this background. Use a transparent SVG (which colorsbymax can colour), or add a version made for ${darkPage ? "dark" : "light"} backgrounds.`,
4728
+ action: "invert"
4729
+ });
4730
+ else if (sample === null) add(img, {
4731
+ kind: "logo-image",
4732
+ title: "Check your logo here",
4733
+ message: `Your logo is a picture, which colorsbymax can’t re-colour. Check it still reads on this ${darkPage ? "dark" : "light"} background, or use a transparent SVG instead.`,
4734
+ action: "invert"
4735
+ });
4736
+ continue;
4737
+ }
4738
+ const painted = ownText(logo) ? logo : [...logo.querySelectorAll("*")].find((n) => ownText(n) || n instanceof SVGElement) ?? logo;
4739
+ const cs = getComputedStyle(painted);
4740
+ const fg = parse(painted instanceof SVGElement ? cs.fill !== "none" ? cs.fill : cs.stroke : cs.color);
4741
+ if (!fg) continue;
4742
+ const ratio = contrastRatio(fg, bg);
4743
+ if (ratio < 3) add(logo, {
4744
+ kind: "logo",
4745
+ title: "Logo is hard to see here",
4746
+ message: logoColouring ? `Your logo reads at only ${fmt(ratio)} on this background. Try another theme, or give the logo its own colour.` : `Your logo keeps its own colours, which read at only ${fmt(ratio)} on this background. Let colorsbymax colour it to match the theme, or pick a theme it suits.`,
4747
+ action: logoColouring ? void 0 : "colour-logo"
4748
+ });
4749
+ }
4750
+ for (const img of document.images) {
4751
+ if (img.closest(SKIP) || !img.complete || !img.naturalWidth || !visible(img)) continue;
4752
+ const rect = img.getBoundingClientRect();
4753
+ if (rect.width < 24 || rect.height < 24) continue;
4754
+ const sample = sampleImage(img);
4755
+ if (!sample || sample.transparent) continue;
4756
+ if (!sample.corners.every((c) => c && contrastRatio(c, sample.corners[0]) < 1.15)) continue;
4757
+ const bg = behind(img.parentElement) ?? pageBg;
4758
+ if (contrastRatio(sample.corners[0], bg) >= 1.6) {
4759
+ const light = luminance(sample.corners[0]) > luminance(bg);
4760
+ add(img, {
4761
+ kind: "image-box",
4762
+ title: `Picture shows as a ${light ? "light" : "dark"} box`,
4763
+ message: `This picture has a solid ${light ? "light" : "dark"} background that stands out against this ${luminance(bg) < .2 ? "dark" : "light"} page. A transparent PNG or an SVG would blend in with any theme.`,
4764
+ action: img.closest(LOGO_SELECTOR) ? "invert" : void 0
4765
+ });
4766
+ }
4767
+ }
4768
+ const elements = [...document.body.querySelectorAll("*")].slice(0, MAX_ELEMENTS);
4769
+ for (const el of elements) {
4770
+ if (el.closest(SKIP) || el.closest(LOGO_SELECTOR)) continue;
4771
+ const svg = el.localName === "svg";
4772
+ if (!svg && !ownText(el)) continue;
4773
+ if (!visible(el)) continue;
4774
+ const cs = getComputedStyle(el);
4775
+ if (cs.backgroundClip === "text" || cs.webkitBackgroundClip === "text") continue;
4776
+ const fg = parse(svg ? cs.stroke !== "none" ? cs.stroke : cs.fill : cs.color);
4777
+ const bg = behind(el);
4778
+ if (!fg || !bg) continue;
4779
+ const size = parseFloat(cs.fontSize);
4780
+ const large = size >= 24 || size >= 18.66 && Number(cs.fontWeight) >= 700;
4781
+ const needed = svg || large ? 3 : 4.5;
4782
+ const ratio = contrastRatio(fg, bg);
4783
+ if (ratio >= needed) continue;
4784
+ add(el, {
4785
+ kind: svg ? "icon" : "text",
4786
+ title: `${svg ? "Icon" : "Text"} is hard to read`,
4787
+ message: `${describe(el) ? `“${describe(el)}” ` : ""}reads at ${fmt(ratio)} here; ${svg ? "icons" : large ? "large text" : "text"} needs ${needed}:1. Try another theme, or adjust it under “Override a single colour”.`
4788
+ });
4789
+ }
4790
+ return findings;
4791
+ }
4792
+ //#endregion
4793
+ //#region src/AuditLayer.jsx
4794
+ var CARD_WIDTH = 264;
4795
+ var GAP = 10;
4796
+ var EDGE$1 = 12;
4797
+ /** Space kept between two notes. */
4798
+ var APART = 8;
4799
+ /** A note's height before it has been measured. */
4800
+ var GUESS_HEIGHT = 150;
4801
+ /** Below this width, only one note is open at a time; the numbered badges pick which. */
4802
+ var COMPACT_WIDTH = 640;
4803
+ /**
4804
+ * The audit on the page: each finding outlined where it is, with a numbered card pointing at it,
4805
+ * kept in place as the page scrolls; and a bar by the colour button to re-check or close.
4806
+ */
4807
+ function AuditLayer({ findings, anchor, themeIssues, onRecheck, onClose, onAction }) {
4808
+ const [rects, setRects] = useState([]);
4809
+ const [dismissed, setDismissed] = useState(() => /* @__PURE__ */ new Set());
4810
+ const [inverted, setInverted] = useState(() => /* @__PURE__ */ new Set());
4811
+ const [selected, setSelected] = useState(null);
4812
+ const [anchorRect, setAnchorRect] = useState(null);
4813
+ const pinned = findings.filter((f) => !dismissed.has(f.id)).slice(0, 8);
4814
+ const hiddenCount = findings.filter((f) => !dismissed.has(f.id)).length - pinned.length;
4815
+ useEffect(() => setDismissed(/* @__PURE__ */ new Set()), [findings]);
4816
+ useLayoutEffect(() => {
4817
+ let frame = 0;
4818
+ const measure = () => {
4819
+ frame = 0;
4820
+ setRects(pinned.map((f) => f.el.isConnected ? f.el.getBoundingClientRect() : null));
4821
+ setAnchorRect(anchor.current?.getBoundingClientRect() ?? null);
4822
+ };
4823
+ const schedule = () => {
4824
+ if (!frame) frame = requestAnimationFrame(measure);
4825
+ };
4826
+ measure();
4827
+ window.addEventListener("scroll", schedule, true);
4828
+ window.addEventListener("resize", schedule);
4829
+ const timer = setInterval(schedule, 500);
4830
+ return () => {
4831
+ cancelAnimationFrame(frame);
4832
+ clearInterval(timer);
4833
+ window.removeEventListener("scroll", schedule, true);
4834
+ window.removeEventListener("resize", schedule);
4835
+ };
4836
+ }, [findings, dismissed]);
4837
+ useEffect(() => {
4838
+ for (const f of findings) f.el.toggleAttribute("data-colorsbymax-invert", inverted.has(f.id));
4839
+ return () => {
4840
+ for (const f of findings) f.el.removeAttribute("data-colorsbymax-invert");
4841
+ };
4842
+ }, [inverted, findings]);
4843
+ useEffect(() => {
4844
+ const style = document.createElement("style");
4845
+ style.dataset.colorsbymax = "audit";
4846
+ style.textContent = "[data-colorsbymax-invert]{filter:invert(1) hue-rotate(180deg)!important}";
4847
+ document.head.append(style);
4848
+ return () => style.remove();
4849
+ }, []);
4850
+ const vw = document.documentElement.clientWidth;
4851
+ const vh = document.documentElement.clientHeight;
4852
+ const open = findings.filter((f) => !dismissed.has(f.id)).length;
4853
+ const heights = useRef({});
4854
+ const layerRef = useRef(null);
4855
+ useLayoutEffect(() => {
4856
+ for (const note of layerRef.current?.querySelectorAll("[data-note]") ?? []) heights.current[note.dataset.note] = note.offsetHeight;
4857
+ });
4858
+ const placed = [];
4859
+ const compact = vw < COMPACT_WIDTH;
4860
+ const openId = pinned.some((f) => f.id === selected) ? selected : pinned[0]?.id;
4861
+ const showsNote = (f) => !compact || f.id === openId;
4862
+ const placements = pinned.map((f, i) => {
4863
+ const r = rects[i];
4864
+ if (!r || r.bottom < 0 || r.top > vh || !showsNote(f)) return null;
4865
+ const h = heights.current[f.id] ?? GUESS_HEIGHT;
4866
+ const below = r.bottom + GAP + h < vh || r.top < h + GAP;
4867
+ let top = below ? r.bottom + GAP : r.top - GAP - h;
4868
+ let left = Math.min(Math.max(r.left + r.width / 2 - CARD_WIDTH / 2, EDGE$1), vw - CARD_WIDTH - EDGE$1);
4869
+ for (let tries = 0; tries < placed.length + 1; tries++) {
4870
+ const hit = placed.find((p) => left < p.left + CARD_WIDTH + APART && left + CARD_WIDTH + APART > p.left && top < p.top + p.h + APART && top + h + APART > p.top);
4871
+ if (!hit) break;
4872
+ if (hit.left + 2 * CARD_WIDTH + APART + EDGE$1 <= vw) left = hit.left + CARD_WIDTH + APART;
4873
+ else top = hit.top + hit.h + APART;
4874
+ }
4875
+ placed.push({
4876
+ left,
4877
+ top,
4878
+ h
4879
+ });
4880
+ const arrow = Math.min(Math.max(r.left + r.width / 2 - left, 16), 248);
4881
+ const pointing = below ? Math.abs(top - (r.bottom + GAP)) < 2 : Math.abs(top + h - (r.top - GAP)) < 2;
4882
+ return {
4883
+ left: Math.round(left),
4884
+ top: Math.round(top),
4885
+ below,
4886
+ arrow: Math.round(arrow),
4887
+ pointing
4888
+ };
4889
+ });
4890
+ return /* @__PURE__ */ jsxs("div", {
4891
+ ref: layerRef,
4892
+ children: [pinned.map((f, i) => {
4893
+ const r = rects[i];
4894
+ if (!r || r.bottom < 0 || r.top > vh) return null;
4895
+ const place = placements[i];
4896
+ return /* @__PURE__ */ jsxs("div", { children: [
4897
+ /* @__PURE__ */ jsx("div", {
4898
+ "aria-hidden": "true",
4899
+ className: "pointer-events-none fixed z-[55] rounded-md border-2 border-dashed border-amber-400",
4900
+ style: {
4901
+ left: r.left - 4,
4902
+ top: r.top - 4,
4903
+ width: r.width + 8,
4904
+ height: r.height + 8
4905
+ }
4906
+ }),
4907
+ compact ? /* @__PURE__ */ jsx("button", {
4908
+ type: "button",
4909
+ "aria-label": `Show audit finding ${i + 1}: ${f.title}`,
4910
+ "aria-pressed": f.id === openId,
4911
+ onClick: () => setSelected(f.id),
4912
+ className: `fixed z-[57] grid h-6 w-6 place-items-center rounded-full bg-amber-400 text-[11px] font-bold text-zinc-900 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-900 ${f.id === openId ? "ring-2 ring-white" : ""}`,
4913
+ style: {
4914
+ left: r.left - 14,
4915
+ top: r.top - 14
4916
+ },
4917
+ children: i + 1
4918
+ }) : /* @__PURE__ */ jsx("span", {
4919
+ "aria-hidden": "true",
4920
+ className: "pointer-events-none fixed z-[56] grid h-5 w-5 place-items-center rounded-full bg-amber-400 text-[11px] font-bold text-zinc-900",
4921
+ style: {
4922
+ left: r.left - 12,
4923
+ top: r.top - 12
4924
+ },
4925
+ children: i + 1
4926
+ }),
4927
+ place && /* @__PURE__ */ jsxs("div", {
4928
+ role: "note",
4929
+ "data-note": f.id,
4930
+ "aria-label": `Audit finding ${i + 1}: ${f.title}`,
4931
+ className: "theme-switcher theme-hint fixed z-[62] rounded-xl border border-zinc-200 bg-white p-3 text-zinc-900 shadow-xl",
4932
+ style: {
4933
+ left: place.left,
4934
+ top: place.top,
4935
+ width: CARD_WIDTH
4936
+ },
4937
+ children: [
4938
+ place.pointing && /* @__PURE__ */ jsx("span", {
4939
+ "aria-hidden": "true",
4940
+ className: `absolute h-2.5 w-2.5 rotate-45 border-zinc-200 bg-white ${place.below ? "-top-[6px] border-l border-t" : "-bottom-[6px] border-b border-r"}`,
4941
+ style: { left: place.arrow - 5 }
4942
+ }),
4943
+ /* @__PURE__ */ jsxs("p", {
4944
+ className: "flex items-start gap-1.5 text-xs font-semibold",
4945
+ children: [/* @__PURE__ */ jsx("span", {
4946
+ className: "grid h-4 w-4 shrink-0 place-items-center rounded-full bg-amber-400 text-[10px] font-bold text-zinc-900",
4947
+ children: i + 1
4948
+ }), f.title]
4949
+ }),
4950
+ /* @__PURE__ */ jsx("p", {
4951
+ className: "mt-1 text-[11px] leading-snug text-zinc-600",
4952
+ children: f.message
4953
+ }),
4954
+ /* @__PURE__ */ jsxs("div", {
4955
+ className: "mt-2 flex flex-wrap gap-1.5",
4956
+ children: [
4957
+ f.action === "colour-logo" && /* @__PURE__ */ jsx("button", {
4958
+ type: "button",
4959
+ className: "rounded-lg bg-zinc-900 px-2 py-1 text-[11px] font-medium text-white hover:bg-zinc-700 cursor-pointer",
4960
+ onClick: () => onAction("colour-logo"),
4961
+ children: "Colour the logo"
4962
+ }),
4963
+ f.action === "invert" && /* @__PURE__ */ jsxs("button", {
4964
+ type: "button",
4965
+ "aria-pressed": inverted.has(f.id),
4966
+ className: "inline-flex items-center gap-1 rounded-lg border border-zinc-300 bg-white px-2 py-1 text-[11px] font-medium text-zinc-800 hover:bg-zinc-50 cursor-pointer",
4967
+ onClick: () => setInverted((s) => {
4968
+ const next = new Set(s);
4969
+ if (next.has(f.id)) next.delete(f.id);
4970
+ else next.add(f.id);
4971
+ return next;
4972
+ }),
4973
+ children: [/* @__PURE__ */ jsx(Contrast, {
4974
+ className: "w-3 h-3",
4975
+ "aria-hidden": "true"
4976
+ }), inverted.has(f.id) ? "Undo preview" : "Preview inverted"]
4977
+ }),
4978
+ /* @__PURE__ */ jsx("button", {
4979
+ type: "button",
4980
+ className: "rounded-lg px-2 py-1 text-[11px] font-medium text-zinc-600 hover:bg-zinc-100 cursor-pointer",
4981
+ onClick: () => setDismissed((s) => new Set(s).add(f.id)),
4982
+ children: "Got it"
4983
+ })
4984
+ ]
4985
+ })
4986
+ ]
4987
+ })
4988
+ ] }, f.id);
4989
+ }), /* @__PURE__ */ jsx(AuditBar, {
4990
+ anchorRect,
4991
+ open,
4992
+ hiddenCount,
4993
+ themeIssues,
4994
+ onRecheck,
4995
+ onClose
4996
+ })]
4997
+ });
4998
+ }
4999
+ /** The audit's summary, next to the colour button: what it found, Re-check and Close. */
5000
+ function AuditBar({ anchorRect, open, hiddenCount, themeIssues, onRecheck, onClose }) {
5001
+ const ref = useRef(null);
5002
+ const [size, setSize] = useState({
5003
+ width: 0,
5004
+ height: 0
5005
+ });
5006
+ useLayoutEffect(() => {
5007
+ const r = ref.current?.getBoundingClientRect();
5008
+ if (r && (r.width !== size.width || r.height !== size.height)) setSize({
5009
+ width: r.width,
5010
+ height: r.height
5011
+ });
5012
+ });
5013
+ const vw = document.documentElement.clientWidth;
5014
+ const vh = document.documentElement.clientHeight;
5015
+ let style = {
5016
+ right: EDGE$1,
5017
+ bottom: EDGE$1
5018
+ };
5019
+ if (anchorRect) {
5020
+ const onRight = anchorRect.left + anchorRect.width / 2 > vw / 2;
5021
+ const top = anchorRect.top + anchorRect.height / 2 < vh / 2 ? Math.round(anchorRect.bottom + GAP) : Math.round(anchorRect.top - GAP - size.height);
5022
+ const left = onRight ? Math.round(anchorRect.right - size.width) : Math.round(anchorRect.left);
5023
+ style = {
5024
+ top,
5025
+ left: Math.min(Math.max(EDGE$1, left), vw - size.width - EDGE$1)
5026
+ };
5027
+ }
5028
+ return /* @__PURE__ */ jsxs("div", {
5029
+ ref,
5030
+ role: "status",
5031
+ "aria-live": "polite",
5032
+ className: "theme-switcher fixed z-[61] flex max-w-[calc(100vw-80px)] items-center gap-2 rounded-full border border-zinc-200 bg-white py-1.5 pl-3 pr-1.5 text-xs text-zinc-900 shadow-xl",
5033
+ style,
5034
+ children: [
5035
+ /* @__PURE__ */ jsx(ScanSearch, {
5036
+ className: "w-4 h-4 shrink-0",
5037
+ "aria-hidden": "true"
5038
+ }),
5039
+ /* @__PURE__ */ jsx("span", {
5040
+ className: "font-semibold",
5041
+ children: "Audit"
5042
+ }),
5043
+ open ? /* @__PURE__ */ jsxs("span", {
5044
+ className: "flex items-center gap-1 text-zinc-700",
5045
+ children: [
5046
+ /* @__PURE__ */ jsx(AlertTriangle, {
5047
+ className: "w-3.5 h-3.5 text-amber-700",
5048
+ "aria-hidden": "true"
5049
+ }),
5050
+ open,
5051
+ " to check",
5052
+ hiddenCount > 0 ? ` (${hiddenCount} more after these)` : ""
5053
+ ]
5054
+ }) : /* @__PURE__ */ jsxs("span", {
5055
+ className: "flex items-center gap-1 text-zinc-700",
5056
+ children: [/* @__PURE__ */ jsx(CheckCircle2, {
5057
+ className: "w-3.5 h-3.5 text-emerald-800",
5058
+ "aria-hidden": "true"
5059
+ }), "Nothing to fix on the page"]
5060
+ }),
5061
+ themeIssues > 0 && /* @__PURE__ */ jsxs("span", {
5062
+ className: "hidden text-zinc-600 sm:inline",
5063
+ children: [
5064
+ "· theme has ",
5065
+ themeIssues,
5066
+ " contrast ",
5067
+ themeIssues === 1 ? "issue" : "issues",
5068
+ " (see the panel)"
5069
+ ]
5070
+ }),
5071
+ /* @__PURE__ */ jsxs("button", {
5072
+ type: "button",
5073
+ onClick: onRecheck,
5074
+ className: "inline-flex items-center gap-1 rounded-full border border-zinc-300 px-2 py-1 text-[11px] font-medium hover:bg-zinc-50 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-900",
5075
+ children: [/* @__PURE__ */ jsx(RotateCcw, {
5076
+ className: "w-3 h-3",
5077
+ "aria-hidden": "true"
5078
+ }), " Re-check"]
5079
+ }),
5080
+ /* @__PURE__ */ jsx("button", {
5081
+ type: "button",
5082
+ onClick: onClose,
5083
+ "aria-label": "Close the audit",
5084
+ className: "grid h-6 w-6 place-items-center rounded-full hover:bg-zinc-100 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-900",
5085
+ children: /* @__PURE__ */ jsx(X, {
5086
+ className: "w-3.5 h-3.5",
5087
+ "aria-hidden": "true"
5088
+ })
5089
+ })
5090
+ ]
5091
+ });
5092
+ }
5093
+ //#endregion
4073
5094
  //#region src/panel-css.generated.js
4074
- var panel_css_generated_default = "/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n@layer properties{@supports (display:block){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-amber-950:oklch(27.9% .077 45.635);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-800:oklch(43.2% .095 166.913);--color-emerald-900:oklch(37.8% .077 168.94);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-100:oklch(95.1% .026 236.824);--color-sky-900:oklch(39.1% .09 240.876);--color-zinc-50:oklch(98.5% 0 none);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-white:#fff;--spacing:4px;--text-xs:12px;--text-xs--line-height:calc(1 / .75);--text-sm:14px;--text-sm--line-height:calc(1.25 / .875);--text-base:16px;--text-base--line-height:calc(1.5 / 1);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wide:.025em;--leading-snug:1.375;--radius-md:6px;--radius-lg:8px;--radius-xl:12px;--radius-2xl:16px;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}.theme-switcher{letter-spacing:normal;color:var(--color-zinc-900);font-family:Inter,ui-sans-serif,system-ui,sans-serif;font-size:16px;line-height:1.5}}@layer components;@layer utilities{.\\@container{container-type:inline-size}.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.inset-x-3{inset-inline:calc(var(--spacing) * 3)}.inset-x-5{inset-inline:calc(var(--spacing) * 5)}.inset-y-5{inset-block:calc(var(--spacing) * 5)}.-top-2{top:calc(var(--spacing) * -2)}.top-0{top:0}.top-1\\.5{top:calc(var(--spacing) * 1.5)}.top-1\\/2{top:50%}.top-5{top:calc(var(--spacing) * 5)}.top-\\[84px\\]{top:84px}.-right-0\\.5{right:calc(var(--spacing) * -.5)}.-right-2{right:calc(var(--spacing) * -2)}.right-1\\.5{right:calc(var(--spacing) * 1.5)}.right-5{right:calc(var(--spacing) * 5)}.right-6{right:calc(var(--spacing) * 6)}.right-full{right:100%}.-bottom-0\\.5{bottom:calc(var(--spacing) * -.5)}.-bottom-2{bottom:calc(var(--spacing) * -2)}.bottom-3{bottom:calc(var(--spacing) * 3)}.bottom-5{bottom:calc(var(--spacing) * 5)}.-left-2{left:calc(var(--spacing) * -2)}.left-5{left:calc(var(--spacing) * 5)}.left-full{left:100%}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-\\[60\\]{z-index:60}.z-\\[70\\]{z-index:70}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.mt-0\\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-2\\.5{margin-top:calc(var(--spacing) * 2.5)}.mt-px{margin-top:1px}.-mr-1{margin-right:calc(var(--spacing) * -1)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mb-1{margin-bottom:var(--spacing)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.h-3{height:calc(var(--spacing) * 3)}.h-3\\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-12{height:calc(var(--spacing) * 12)}.h-px{height:1px}.min-h-0{min-height:0}.w-3{width:calc(var(--spacing) * 3)}.w-3\\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-7{width:calc(var(--spacing) * 7)}.w-9{width:calc(var(--spacing) * 9)}.w-16{width:calc(var(--spacing) * 16)}.w-\\[5\\.5rem\\]{width:88px}.w-full{width:100%}.max-w-\\[240px\\]{max-width:240px}.max-w-full{max-width:100%}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-y-1\\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.scale-110{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.rotate-90{rotate:90deg}.animate-spin{animation:var(--animate-spin)}.cursor-ew-resize{cursor:ew-resize}.cursor-grabbing{cursor:grabbing}.cursor-nesw-resize{cursor:nesw-resize}.cursor-ns-resize{cursor:ns-resize}.cursor-nwse-resize{cursor:nwse-resize}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.resize{resize:both}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-0\\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.rounded{border-radius:4px}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-\\[inherit\\]{border-radius:inherit}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-300{border-color:var(--color-amber-300)}.border-red-600{border-color:var(--color-red-600)}.border-transparent{border-color:#0000}.border-white{border-color:var(--color-white)}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-300{border-color:var(--color-zinc-300)}.border-zinc-900{border-color:var(--color-zinc-900)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-sky-50{background-color:var(--color-sky-50)}.bg-sky-100{background-color:var(--color-sky-100)}.bg-white{background-color:var(--color-white)}.bg-white\\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab, red, red)){.bg-white\\/90{background-color:color-mix(in oklab, var(--color-white) 90%, transparent)}}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-200{background-color:var(--color-zinc-200)}.bg-zinc-900{background-color:var(--color-zinc-900)}.p-0\\.5{padding:calc(var(--spacing) * .5)}.p-2\\.5{padding:calc(var(--spacing) * 2.5)}.p-3{padding:calc(var(--spacing) * 3)}.px-1{padding-inline:var(--spacing)}.px-1\\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.pr-12{padding-right:calc(var(--spacing) * 12)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pl-1{padding-left:var(--spacing)}.text-center{text-align:center}.text-left{text-align:left}.align-super{vertical-align:super}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\\[10px\\]{font-size:10px}.text-\\[11px\\]{font-size:11px}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.whitespace-nowrap{white-space:nowrap}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-amber-900{color:var(--color-amber-900)}.text-amber-950{color:var(--color-amber-950)}.text-emerald-800{color:var(--color-emerald-800)}.text-emerald-900{color:var(--color-emerald-900)}.text-red-700{color:var(--color-red-700)}.text-sky-900{color:var(--color-sky-900)}.text-white{color:var(--color-white)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-700{color:var(--color-zinc-700)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.accent-zinc-900{accent-color:var(--color-zinc-900)}.opacity-25{opacity:.25}.opacity-70{opacity:.7}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-zinc-900{--tw-ring-color:var(--color-zinc-900)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-\\[color\\,background-color\\,box-shadow\\,scale\\]{transition-property:color,background-color,box-shadow,scale;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-700{--tw-duration:.7s;transition-duration:.7s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.select-none{-webkit-user-select:none;user-select:none}.placeholder\\:text-zinc-500::placeholder{color:var(--color-zinc-500)}@media (hover:hover){.hover\\:bg-amber-200:hover{background-color:var(--color-amber-200)}.hover\\:bg-white:hover{background-color:var(--color-white)}.hover\\:bg-zinc-50:hover{background-color:var(--color-zinc-50)}.hover\\:bg-zinc-200:hover{background-color:var(--color-zinc-200)}.hover\\:bg-zinc-400\\/30:hover{background-color:#9f9fa94d}@supports (color:color-mix(in lab, red, red)){.hover\\:bg-zinc-400\\/30:hover{background-color:color-mix(in oklab, var(--color-zinc-400) 30%, transparent)}}.hover\\:bg-zinc-700:hover{background-color:var(--color-zinc-700)}.hover\\:text-amber-950:hover{color:var(--color-amber-950)}.hover\\:text-zinc-900:hover{color:var(--color-zinc-900)}.hover\\:opacity-100:hover{opacity:1}}.focus\\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\\:ring-white:focus-visible{--tw-ring-color:var(--color-white)}.focus-visible\\:ring-zinc-900:focus-visible{--tw-ring-color:var(--color-zinc-900)}.focus-visible\\:ring-offset-1:focus-visible{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.focus-visible\\:ring-inset:focus-visible{--tw-ring-inset:inset}.disabled\\:opacity-50:disabled{opacity:.5}@media (prefers-reduced-motion:reduce){.motion-reduce\\:animate-none{animation:none}.motion-reduce\\:transition-none{transition-property:none}}@media (min-width:1200px){.min-\\[1200px\\]\\:top-\\[27px\\]{top:27px}.min-\\[1200px\\]\\:right-4{right:calc(var(--spacing) * 4)}}@container (min-width:380px){.\\@min-\\[380px\\]\\:h-10{height:calc(var(--spacing) * 10)}.\\@min-\\[380px\\]\\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.\\@min-\\[380px\\]\\:flex-row{flex-direction:row}.\\@min-\\[380px\\]\\:justify-start{justify-content:flex-start}.\\@min-\\[380px\\]\\:gap-1\\.5{gap:calc(var(--spacing) * 1.5)}}@container (min-width:440px){.\\@min-\\[440px\\]\\:inline{display:inline}.\\@min-\\[440px\\]\\:px-2\\.5{padding-inline:calc(var(--spacing) * 2.5)}}@container (min-width:520px){.\\@min-\\[520px\\]\\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}}:host{all:initial}.cbm-dark{color-scheme:dark;--color-white:oklch(21% .006 285.885);--color-zinc-50:oklch(24.5% .006 286);--color-zinc-100:oklch(27.4% .006 286.033);--color-zinc-200:oklch(33% .01 285.9);--color-zinc-300:oklch(42% .015 285.8);--color-zinc-400:oklch(52% .016 285.9);--color-zinc-500:oklch(68% .015 286.067);--color-zinc-600:oklch(74% .012 286);--color-zinc-700:oklch(82% .008 286.2);--color-zinc-800:oklch(88% .006 286.3);--color-zinc-900:oklch(96.7% .001 286.375);--color-zinc-950:oklch(98.5% 0 0);--color-amber-50:oklch(27.9% .077 45.635);--color-amber-100:oklch(33% .09 46);--color-amber-200:oklch(41.4% .112 45.904);--color-amber-300:oklch(55.5% .163 48.998);--color-amber-700:oklch(87.9% .169 91.605);--color-amber-800:oklch(92.4% .12 95.746);--color-amber-900:oklch(96.2% .059 95.617);--color-amber-950:oklch(98.7% .022 95.277);--color-emerald-50:oklch(26.2% .051 172.552);--color-emerald-800:oklch(90.5% .093 164.15);--color-emerald-900:oklch(95% .052 163.051);--color-sky-50:oklch(29.3% .066 243.157);--color-sky-100:oklch(39.1% .09 240.876);--color-sky-900:oklch(95.1% .026 236.824);--color-red-600:oklch(70.4% .191 22.216);--color-red-700:oklch(80.8% .114 19.571)}.cbm-dark *{--tw-ring-offset-color:oklch(21% .006 285.885)}.theme-scroll{--cbm-thumb:var(--color-primary,oklch(55.2% .016 285.938))}@supports (color:color-mix(in lab, red, red)){.theme-scroll{--cbm-thumb:color-mix(in srgb, var(--color-primary,var(--color-zinc-500)) 55%, var(--color-white))}}.theme-scroll{--cbm-thumb-hover:var(--color-primary,oklch(55.2% .016 285.938))}@supports (color:color-mix(in lab, red, red)){.theme-scroll{--cbm-thumb-hover:color-mix(in srgb, var(--color-primary,var(--color-zinc-500)) 80%, var(--color-white))}}.theme-scroll{scrollbar-width:thin;scrollbar-color:var(--cbm-thumb) transparent}.theme-scroll:hover{scrollbar-color:var(--cbm-thumb-hover) transparent}.theme-scroll::-webkit-scrollbar{width:6px}.theme-scroll::-webkit-scrollbar-track{background:0 0}.theme-scroll::-webkit-scrollbar-thumb{background:var(--cbm-thumb);border-radius:9999px}.theme-scroll:hover::-webkit-scrollbar-thumb{background:var(--cbm-thumb-hover)}.theme-panel{transform-origin:100% 0;animation:.16s ease-out theme-panel-in}@keyframes theme-panel-in{0%{opacity:0;transform:translateY(-6px)scale(.98)}}.theme-hint{animation:.12s ease-out theme-hint-in}.theme-toast{animation:.18s ease-out theme-toast-in}@keyframes theme-toast-in{0%{opacity:0;transform:translateY(8px)}}@keyframes theme-hint-in{0%{opacity:0}}@media (prefers-reduced-motion:reduce){.theme-panel,.theme-hint,.theme-toast{animation:none}}.theme-panel summary{list-style:none}.theme-panel summary::-webkit-details-marker{display:none}.theme-panel details[open]>summary .theme-chevron{transform:rotate(90deg)}@keyframes spin{to{transform:rotate(360deg)}}";
5095
+ var panel_css_generated_default = "/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n@layer properties{@supports (display:block){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-amber-950:oklch(27.9% .077 45.635);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-800:oklch(43.2% .095 166.913);--color-emerald-900:oklch(37.8% .077 168.94);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-100:oklch(95.1% .026 236.824);--color-sky-900:oklch(39.1% .09 240.876);--color-zinc-50:oklch(98.5% 0 none);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-white:#fff;--spacing:4px;--text-xs:12px;--text-xs--line-height:calc(1 / .75);--text-sm:14px;--text-sm--line-height:calc(1.25 / .875);--text-base:16px;--text-base--line-height:calc(1.5 / 1);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wide:.025em;--leading-snug:1.375;--radius-md:6px;--radius-lg:8px;--radius-xl:12px;--radius-2xl:16px;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}.theme-switcher{letter-spacing:normal;color:var(--color-zinc-900);font-family:Inter,ui-sans-serif,system-ui,sans-serif;font-size:16px;line-height:1.5}}@layer components;@layer utilities{.\\@container{container-type:inline-size}.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.inset-x-3{inset-inline:calc(var(--spacing) * 3)}.inset-x-5{inset-inline:calc(var(--spacing) * 5)}.inset-y-5{inset-block:calc(var(--spacing) * 5)}.-top-2{top:calc(var(--spacing) * -2)}.-top-\\[6px\\]{top:-6px}.top-0{top:0}.top-1\\.5{top:calc(var(--spacing) * 1.5)}.top-1\\/2{top:50%}.top-5{top:calc(var(--spacing) * 5)}.top-\\[84px\\]{top:84px}.-right-0\\.5{right:calc(var(--spacing) * -.5)}.-right-2{right:calc(var(--spacing) * -2)}.right-1\\.5{right:calc(var(--spacing) * 1.5)}.right-5{right:calc(var(--spacing) * 5)}.right-6{right:calc(var(--spacing) * 6)}.right-full{right:100%}.-bottom-0\\.5{bottom:calc(var(--spacing) * -.5)}.-bottom-2{bottom:calc(var(--spacing) * -2)}.-bottom-\\[6px\\]{bottom:-6px}.bottom-3{bottom:calc(var(--spacing) * 3)}.bottom-5{bottom:calc(var(--spacing) * 5)}.-left-2{left:calc(var(--spacing) * -2)}.left-5{left:calc(var(--spacing) * 5)}.left-full{left:100%}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-\\[55\\]{z-index:55}.z-\\[56\\]{z-index:56}.z-\\[57\\]{z-index:57}.z-\\[60\\]{z-index:60}.z-\\[61\\]{z-index:61}.z-\\[62\\]{z-index:62}.z-\\[70\\]{z-index:70}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.mt-0\\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-2\\.5{margin-top:calc(var(--spacing) * 2.5)}.mt-px{margin-top:1px}.-mr-1{margin-right:calc(var(--spacing) * -1)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mb-1{margin-bottom:var(--spacing)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.h-2\\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-3\\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-12{height:calc(var(--spacing) * 12)}.h-px{height:1px}.max-h-40{max-height:calc(var(--spacing) * 40)}.min-h-0{min-height:0}.w-2\\.5{width:calc(var(--spacing) * 2.5)}.w-3{width:calc(var(--spacing) * 3)}.w-3\\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-9{width:calc(var(--spacing) * 9)}.w-16{width:calc(var(--spacing) * 16)}.w-\\[5\\.5rem\\]{width:88px}.w-full{width:100%}.max-w-\\[240px\\]{max-width:240px}.max-w-\\[calc\\(100vw-80px\\)\\]{max-width:calc(100vw - 80px)}.max-w-full{max-width:100%}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-y-1\\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.scale-110{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.rotate-45{rotate:45deg}.rotate-90{rotate:90deg}.animate-spin{animation:var(--animate-spin)}.cursor-ew-resize{cursor:ew-resize}.cursor-grabbing{cursor:grabbing}.cursor-nesw-resize{cursor:nesw-resize}.cursor-ns-resize{cursor:ns-resize}.cursor-nwse-resize{cursor:nwse-resize}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.resize{resize:both}.list-decimal{list-style-type:decimal}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-0\\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.rounded{border-radius:4px}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-\\[inherit\\]{border-radius:inherit}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-300{border-color:var(--color-amber-300)}.border-amber-400{border-color:var(--color-amber-400)}.border-red-600{border-color:var(--color-red-600)}.border-transparent{border-color:#0000}.border-white{border-color:var(--color-white)}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-300{border-color:var(--color-zinc-300)}.border-zinc-900{border-color:var(--color-zinc-900)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-amber-400{background-color:var(--color-amber-400)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-sky-50{background-color:var(--color-sky-50)}.bg-sky-100{background-color:var(--color-sky-100)}.bg-white{background-color:var(--color-white)}.bg-white\\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab, red, red)){.bg-white\\/90{background-color:color-mix(in oklab, var(--color-white) 90%, transparent)}}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-200{background-color:var(--color-zinc-200)}.bg-zinc-900{background-color:var(--color-zinc-900)}.p-0\\.5{padding:calc(var(--spacing) * .5)}.p-2\\.5{padding:calc(var(--spacing) * 2.5)}.p-3{padding:calc(var(--spacing) * 3)}.px-1{padding-inline:var(--spacing)}.px-1\\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.pr-1\\.5{padding-right:calc(var(--spacing) * 1.5)}.pr-12{padding-right:calc(var(--spacing) * 12)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pl-1{padding-left:var(--spacing)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-4{padding-left:calc(var(--spacing) * 4)}.text-center{text-align:center}.text-left{text-align:left}.align-super{vertical-align:super}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\\[10px\\]{font-size:10px}.text-\\[11px\\]{font-size:11px}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-amber-900{color:var(--color-amber-900)}.text-amber-950{color:var(--color-amber-950)}.text-emerald-800{color:var(--color-emerald-800)}.text-emerald-900{color:var(--color-emerald-900)}.text-red-700{color:var(--color-red-700)}.text-sky-900{color:var(--color-sky-900)}.text-white{color:var(--color-white)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-700{color:var(--color-zinc-700)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.accent-zinc-900{accent-color:var(--color-zinc-900)}.opacity-25{opacity:.25}.opacity-70{opacity:.7}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-white{--tw-ring-color:var(--color-white)}.ring-zinc-900{--tw-ring-color:var(--color-zinc-900)}.invert{--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-\\[color\\,background-color\\,box-shadow\\,scale\\]{transition-property:color,background-color,box-shadow,scale;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-700{--tw-duration:.7s;transition-duration:.7s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.select-none{-webkit-user-select:none;user-select:none}.placeholder\\:text-zinc-500::placeholder{color:var(--color-zinc-500)}@media (hover:hover){.hover\\:bg-amber-200:hover{background-color:var(--color-amber-200)}.hover\\:bg-white:hover{background-color:var(--color-white)}.hover\\:bg-zinc-50:hover{background-color:var(--color-zinc-50)}.hover\\:bg-zinc-100:hover{background-color:var(--color-zinc-100)}.hover\\:bg-zinc-200:hover{background-color:var(--color-zinc-200)}.hover\\:bg-zinc-400\\/30:hover{background-color:#9f9fa94d}@supports (color:color-mix(in lab, red, red)){.hover\\:bg-zinc-400\\/30:hover{background-color:color-mix(in oklab, var(--color-zinc-400) 30%, transparent)}}.hover\\:bg-zinc-700:hover{background-color:var(--color-zinc-700)}.hover\\:text-amber-950:hover{color:var(--color-amber-950)}.hover\\:text-zinc-900:hover{color:var(--color-zinc-900)}.hover\\:opacity-100:hover{opacity:1}}.focus\\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\\:ring-white:focus-visible{--tw-ring-color:var(--color-white)}.focus-visible\\:ring-zinc-900:focus-visible{--tw-ring-color:var(--color-zinc-900)}.focus-visible\\:ring-offset-1:focus-visible{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.focus-visible\\:ring-inset:focus-visible{--tw-ring-inset:inset}.disabled\\:opacity-50:disabled{opacity:.5}@media (prefers-reduced-motion:reduce){.motion-reduce\\:animate-none{animation:none}.motion-reduce\\:transition-none{transition-property:none}}@media (min-width:1200px){.min-\\[1200px\\]\\:top-\\[27px\\]{top:27px}.min-\\[1200px\\]\\:right-4{right:calc(var(--spacing) * 4)}}@media (min-width:640px){.sm\\:inline{display:inline}}@container (min-width:380px){.\\@min-\\[380px\\]\\:h-10{height:calc(var(--spacing) * 10)}.\\@min-\\[380px\\]\\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.\\@min-\\[380px\\]\\:flex-row{flex-direction:row}.\\@min-\\[380px\\]\\:justify-start{justify-content:flex-start}.\\@min-\\[380px\\]\\:gap-1\\.5{gap:calc(var(--spacing) * 1.5)}}@container (min-width:440px){.\\@min-\\[440px\\]\\:inline{display:inline}.\\@min-\\[440px\\]\\:px-2\\.5{padding-inline:calc(var(--spacing) * 2.5)}}@container (min-width:520px){.\\@min-\\[520px\\]\\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}}:host{all:initial}.cbm-dark{color-scheme:dark;--color-white:oklch(21% .006 285.885);--color-zinc-50:oklch(24.5% .006 286);--color-zinc-100:oklch(27.4% .006 286.033);--color-zinc-200:oklch(33% .01 285.9);--color-zinc-300:oklch(42% .015 285.8);--color-zinc-400:oklch(52% .016 285.9);--color-zinc-500:oklch(68% .015 286.067);--color-zinc-600:oklch(74% .012 286);--color-zinc-700:oklch(82% .008 286.2);--color-zinc-800:oklch(88% .006 286.3);--color-zinc-900:oklch(96.7% .001 286.375);--color-zinc-950:oklch(98.5% 0 0);--color-amber-50:oklch(27.9% .077 45.635);--color-amber-100:oklch(33% .09 46);--color-amber-200:oklch(41.4% .112 45.904);--color-amber-300:oklch(55.5% .163 48.998);--color-amber-700:oklch(87.9% .169 91.605);--color-amber-800:oklch(92.4% .12 95.746);--color-amber-900:oklch(96.2% .059 95.617);--color-amber-950:oklch(98.7% .022 95.277);--color-emerald-50:oklch(26.2% .051 172.552);--color-emerald-800:oklch(90.5% .093 164.15);--color-emerald-900:oklch(95% .052 163.051);--color-sky-50:oklch(29.3% .066 243.157);--color-sky-100:oklch(39.1% .09 240.876);--color-sky-900:oklch(95.1% .026 236.824);--color-red-600:oklch(70.4% .191 22.216);--color-red-700:oklch(80.8% .114 19.571)}.cbm-dark *{--tw-ring-offset-color:oklch(21% .006 285.885)}.theme-scroll{--cbm-thumb:var(--color-primary,oklch(55.2% .016 285.938))}@supports (color:color-mix(in lab, red, red)){.theme-scroll{--cbm-thumb:color-mix(in srgb, var(--color-primary,var(--color-zinc-500)) 55%, var(--color-white))}}.theme-scroll{--cbm-thumb-hover:var(--color-primary,oklch(55.2% .016 285.938))}@supports (color:color-mix(in lab, red, red)){.theme-scroll{--cbm-thumb-hover:color-mix(in srgb, var(--color-primary,var(--color-zinc-500)) 80%, var(--color-white))}}.theme-scroll{scrollbar-width:thin;scrollbar-color:var(--cbm-thumb) transparent}.theme-scroll:hover{scrollbar-color:var(--cbm-thumb-hover) transparent}.theme-scroll::-webkit-scrollbar{width:6px}.theme-scroll::-webkit-scrollbar-track{background:0 0}.theme-scroll::-webkit-scrollbar-thumb{background:var(--cbm-thumb);border-radius:9999px}.theme-scroll:hover::-webkit-scrollbar-thumb{background:var(--cbm-thumb-hover)}.theme-panel{transform-origin:100% 0;animation:.16s ease-out theme-panel-in}@keyframes theme-panel-in{0%{opacity:0;transform:translateY(-6px)scale(.98)}}.theme-hint{animation:.12s ease-out theme-hint-in}.theme-toast{animation:.18s ease-out theme-toast-in}@keyframes theme-toast-in{0%{opacity:0;transform:translateY(8px)}}@keyframes theme-hint-in{0%{opacity:0}}@media (prefers-reduced-motion:reduce){.theme-panel,.theme-hint,.theme-toast{animation:none}}.theme-panel summary{list-style:none}.theme-panel summary::-webkit-details-marker{display:none}.theme-panel details[open]>summary .theme-chevron{transform:rotate(90deg)}@keyframes spin{to{transform:rotate(360deg)}}";
4075
5096
  //#endregion
4076
5097
  //#region src/ThemeSwitcher.jsx
4077
5098
  var CORNERS = {
@@ -4098,8 +5119,10 @@ var HOST_TAG = "colorsbymax-root";
4098
5119
  * needs nothing from the host site's CSS and the host's CSS can't restyle it.
4099
5120
  */
4100
5121
  function ThemeSwitcher() {
5122
+ const { hidden } = useTheme();
4101
5123
  const [mount, setMount] = useState(null);
4102
5124
  useEffect(() => {
5125
+ if (hidden) return;
4103
5126
  const host = document.createElement(HOST_TAG);
4104
5127
  const shadow = host.attachShadow({ mode: "open" });
4105
5128
  const style = document.createElement("style");
@@ -4108,15 +5131,54 @@ function ThemeSwitcher() {
4108
5131
  shadow.append(style, container);
4109
5132
  document.body.append(host);
4110
5133
  setMount(container);
4111
- return () => host.remove();
4112
- }, []);
5134
+ return () => {
5135
+ host.remove();
5136
+ setMount(null);
5137
+ };
5138
+ }, [hidden]);
4113
5139
  return mount && createPortal(/* @__PURE__ */ jsx(Switcher, {}), mount);
4114
5140
  }
4115
5141
  function Switcher() {
4116
- const { issues, storageKey, tokens, active, themes, selectTheme, position: requested } = useTheme();
5142
+ const { issues, storageKey, tokens, active, themes, selectTheme, position: requested, setLogoColouring } = useTheme();
4117
5143
  const position = CORNERS[requested] ? requested : "bottom-right";
4118
5144
  const [open, setOpen] = useState(false);
4119
5145
  const [settings, setSettings] = useState(() => loadSettings(storageKey));
5146
+ useEffect(() => setLogoColouring(settings.colourLogo), [settings.colourLogo, setLogoColouring]);
5147
+ const [auditOn, setAuditOn] = useState(false);
5148
+ const [findings, setFindings] = useState([]);
5149
+ const recheck = useCallback(() => {
5150
+ requestAnimationFrame(() => setFindings(runAudit({ logoColouring: settingsRef.current.colourLogo })));
5151
+ }, []);
5152
+ const settingsRef = useRef(settings);
5153
+ settingsRef.current = settings;
5154
+ useEffect(() => {
5155
+ if (!auditOn) return;
5156
+ const timer = setTimeout(recheck, 150);
5157
+ return () => clearTimeout(timer);
5158
+ }, [
5159
+ auditOn,
5160
+ tokens,
5161
+ settings.colourLogo,
5162
+ recheck
5163
+ ]);
5164
+ useEffect(() => {
5165
+ const setHidden = (hide) => setSettings((s) => s.hideButton === hide ? s : storeSettings({
5166
+ ...s,
5167
+ hideButton: hide
5168
+ }));
5169
+ if (new URLSearchParams(window.location.search).has("colorsbymax")) setHidden(false);
5170
+ const onKey = (e) => {
5171
+ if (e.altKey && e.shiftKey && e.code === "KeyC") {
5172
+ e.preventDefault();
5173
+ setSettings((s) => storeSettings({
5174
+ ...s,
5175
+ hideButton: !s.hideButton
5176
+ }));
5177
+ }
5178
+ };
5179
+ window.addEventListener("keydown", onKey);
5180
+ return () => window.removeEventListener("keydown", onKey);
5181
+ }, []);
4120
5182
  const prefersDark = usePrefersDark();
4121
5183
  const mode = settings.mode === "system" ? prefersDark ? "dark" : "light" : settings.mode;
4122
5184
  const buttonRef = useRef(null);
@@ -4257,6 +5319,18 @@ function Switcher() {
4257
5319
  const settingsApi = {
4258
5320
  settings,
4259
5321
  mode,
5322
+ audit: {
5323
+ on: auditOn,
5324
+ toggle() {
5325
+ if (auditOn) {
5326
+ setAuditOn(false);
5327
+ setFindings([]);
5328
+ } else {
5329
+ setOpen(false);
5330
+ setAuditOn(true);
5331
+ }
5332
+ }
5333
+ },
4260
5334
  update: (patch) => setSettings((s) => storeSettings({
4261
5335
  ...s,
4262
5336
  ...patch
@@ -4327,6 +5401,7 @@ function Switcher() {
4327
5401
  document.removeEventListener("pointerdown", onPointerDown);
4328
5402
  };
4329
5403
  }, [open, close]);
5404
+ if (settings.hideButton) return null;
4330
5405
  return /* @__PURE__ */ jsx(SettingsContext.Provider, {
4331
5406
  value: settingsApi,
4332
5407
  children: /* @__PURE__ */ jsxs("div", {
@@ -4389,6 +5464,19 @@ function Switcher() {
4389
5464
  active: Boolean(liveSize)
4390
5465
  })]
4391
5466
  }),
5467
+ auditOn && /* @__PURE__ */ jsx(AuditLayer, {
5468
+ findings,
5469
+ anchor: buttonRef,
5470
+ themeIssues: issues.length,
5471
+ onRecheck: recheck,
5472
+ onClose: () => {
5473
+ setAuditOn(false);
5474
+ setFindings([]);
5475
+ },
5476
+ onAction: (action) => {
5477
+ if (action === "colour-logo") settingsApi.update({ colourLogo: true });
5478
+ }
5479
+ }),
4392
5480
  /* @__PURE__ */ jsx(TooltipLayer, { rootRef: layerRef })
4393
5481
  ]
4394
5482
  })