@salesmind-ai/design-system 0.6.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/Anton-Regular-MLEXVTB2.woff2 +0 -0
- package/dist/admin/index.cjs +5 -61
- package/dist/admin/index.css +0 -3918
- package/dist/admin/index.css.map +1 -1
- package/dist/admin/index.d.cts +1 -422
- package/dist/admin/index.d.ts +1 -422
- package/dist/admin/index.js +1 -5
- package/dist/blog/index.cjs +13 -34
- package/dist/blog/index.css +0 -579
- package/dist/blog/index.css.map +1 -1
- package/dist/blog/index.d.cts +1 -54
- package/dist/blog/index.d.ts +1 -54
- package/dist/blog/index.js +5 -6
- package/dist/charts/index.cjs +0 -46
- package/dist/charts/index.d.cts +1 -452
- package/dist/charts/index.d.ts +1 -452
- package/dist/charts/index.js +1 -3
- package/dist/{chunk-YTYDQBVY.cjs → chunk-2VVRZBUR.cjs} +4 -4
- package/dist/{chunk-GQELL2MF.cjs → chunk-3NS6X2R4.cjs} +20 -203
- package/dist/chunk-3NS6X2R4.cjs.map +1 -0
- package/dist/{chunk-XEX2AEZK.cjs → chunk-65DTHLVX.cjs} +66 -186
- package/dist/chunk-65DTHLVX.cjs.map +1 -0
- package/dist/{chunk-QALDZ7WQ.js → chunk-6BUS7RMS.js} +21 -198
- package/dist/chunk-6BUS7RMS.js.map +1 -0
- package/dist/{chunk-LTPTW2US.cjs → chunk-6BXKRDK5.cjs} +587 -977
- package/dist/chunk-6BXKRDK5.cjs.map +1 -0
- package/dist/{chunk-BJZ2DKS5.cjs → chunk-6QIQCUYC.cjs} +11 -10
- package/dist/chunk-6QIQCUYC.cjs.map +1 -0
- package/dist/{chunk-H2Y6BSTL.cjs → chunk-7EUR3AKV.cjs} +1 -1
- package/dist/chunk-7EUR3AKV.cjs.map +1 -0
- package/dist/{chunk-VFJZQQZU.js → chunk-AMNY5TS3.js} +11 -10
- package/dist/chunk-AMNY5TS3.js.map +1 -0
- package/dist/{chunk-YJ6C3EKW.js → chunk-CLXLQCNQ.js} +52 -168
- package/dist/chunk-CLXLQCNQ.js.map +1 -0
- package/dist/{chunk-H2KQ3WSH.cjs → chunk-CVLD5RQK.cjs} +12 -14
- package/dist/chunk-CVLD5RQK.cjs.map +1 -0
- package/dist/chunk-EPD4ZEPY.cjs +344 -0
- package/dist/chunk-EPD4ZEPY.cjs.map +1 -0
- package/dist/{chunk-6D22TFLA.cjs → chunk-FVSL5YMB.cjs} +16 -32
- package/dist/chunk-FVSL5YMB.cjs.map +1 -0
- package/dist/chunk-FXYOSA4E.cjs +118 -0
- package/dist/chunk-FXYOSA4E.cjs.map +1 -0
- package/dist/{chunk-ECXBTUH6.cjs → chunk-GPHQGLR5.cjs} +27 -204
- package/dist/chunk-GPHQGLR5.cjs.map +1 -0
- package/dist/chunk-JPUJWI7F.cjs +73 -0
- package/dist/chunk-JPUJWI7F.cjs.map +1 -0
- package/dist/{chunk-6UNG76Y2.js → chunk-K526GN7P.js} +2 -2
- package/dist/{chunk-SICKWUWB.js → chunk-KJHPOB3J.js} +1 -1
- package/dist/chunk-KJHPOB3J.js.map +1 -0
- package/dist/{chunk-Y26OHHMX.js → chunk-KSEETC4E.js} +508 -891
- package/dist/chunk-KSEETC4E.js.map +1 -0
- package/dist/chunk-KXVFFEGD.js +60 -0
- package/dist/chunk-KXVFFEGD.js.map +1 -0
- package/dist/chunk-LQB7QLD3.js +288 -0
- package/dist/chunk-LQB7QLD3.js.map +1 -0
- package/dist/chunk-LUD52ZJF.cjs +726 -0
- package/dist/chunk-LUD52ZJF.cjs.map +1 -0
- package/dist/{chunk-7UZ5DETZ.js → chunk-MBAG654R.js} +4 -216
- package/dist/chunk-MBAG654R.js.map +1 -0
- package/dist/chunk-OMP6FAZ6.cjs +183 -0
- package/dist/chunk-OMP6FAZ6.cjs.map +1 -0
- package/dist/{chunk-WYH4TKS5.js → chunk-PBYRTNQ5.js} +6 -8
- package/dist/chunk-PBYRTNQ5.js.map +1 -0
- package/dist/chunk-PYREXCZK.js +679 -0
- package/dist/chunk-PYREXCZK.js.map +1 -0
- package/dist/{chunk-P5BOFE5A.js → chunk-RSLA2FJN.js} +28 -183
- package/dist/chunk-RSLA2FJN.js.map +1 -0
- package/dist/chunk-S46SKHMD.js +173 -0
- package/dist/chunk-S46SKHMD.js.map +1 -0
- package/dist/chunk-SFXTB7JL.js +190 -0
- package/dist/chunk-SFXTB7JL.js.map +1 -0
- package/dist/chunk-SGYXYMKZ.cjs +214 -0
- package/dist/chunk-SGYXYMKZ.cjs.map +1 -0
- package/dist/chunk-UGKYP6F3.cjs +296 -0
- package/dist/chunk-UGKYP6F3.cjs.map +1 -0
- package/dist/{chunk-HDVAMYSG.js → chunk-VFO2MUPI.js} +14 -29
- package/dist/chunk-VFO2MUPI.js.map +1 -0
- package/dist/chunk-WB6XDNU7.js +115 -0
- package/dist/chunk-WB6XDNU7.js.map +1 -0
- package/dist/core/index.cjs +144 -626
- package/dist/core/index.css +178 -3567
- package/dist/core/index.css.map +1 -1
- package/dist/core/index.d.cts +940 -902
- package/dist/core/index.d.ts +940 -902
- package/dist/core/index.js +6 -12
- package/dist/i18n/index.cjs +54 -49
- package/dist/i18n/index.d.cts +46 -11
- package/dist/i18n/index.d.ts +46 -11
- package/dist/i18n/index.js +2 -1
- package/dist/index-C8A3X92-.d.cts +1100 -0
- package/dist/index-wZPBPkOV.d.ts +1100 -0
- package/dist/index.cjs +506 -1004
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +6026 -16790
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +307 -22
- package/dist/index.d.ts +307 -22
- package/dist/index.js +274 -39
- package/dist/index.js.map +1 -1
- package/dist/marketing/index.cjs +33 -76
- package/dist/marketing/index.css +1896 -3234
- package/dist/marketing/index.css.map +1 -1
- package/dist/marketing/index.d.cts +3 -1351
- package/dist/marketing/index.d.ts +3 -1351
- package/dist/marketing/index.js +5 -8
- package/dist/motion/index.cjs +3 -20
- package/dist/motion/index.css +0 -580
- package/dist/motion/index.css.map +1 -1
- package/dist/motion/index.d.cts +1 -37
- package/dist/motion/index.d.ts +1 -37
- package/dist/motion/index.js +1 -2
- package/dist/nav/index.cjs +10 -35
- package/dist/nav/index.css +28 -580
- package/dist/nav/index.css.map +1 -1
- package/dist/nav/index.d.cts +2 -60
- package/dist/nav/index.d.ts +2 -60
- package/dist/nav/index.js +1 -2
- package/dist/report/index.cjs +1166 -175
- package/dist/report/index.cjs.map +1 -1
- package/dist/report/index.d.cts +208 -5
- package/dist/report/index.d.ts +208 -5
- package/dist/report/index.js +1141 -3
- package/dist/report/index.js.map +1 -1
- package/dist/sections/index.cjs +8 -10
- package/dist/sections/index.cjs.map +1 -1
- package/dist/sections/index.css +0 -206
- package/dist/sections/index.css.map +1 -1
- package/dist/sections/index.js +2 -4
- package/dist/sections/index.js.map +1 -1
- package/dist/social-media/index.cjs +4 -0
- package/dist/social-media/index.cjs.map +1 -0
- package/dist/social-media/index.d.cts +2 -0
- package/dist/social-media/index.d.ts +2 -0
- package/dist/social-media/index.js +3 -0
- package/dist/social-media/index.js.map +1 -0
- package/dist/social-proof/index.cjs +4 -36
- package/dist/social-proof/index.css +3 -1106
- package/dist/social-proof/index.css.map +1 -1
- package/dist/social-proof/index.d.cts +26 -171
- package/dist/social-proof/index.d.ts +26 -171
- package/dist/social-proof/index.js +1 -5
- package/dist/styles/styles.css +602 -3044
- package/dist/theme/index.cjs +11 -19
- package/dist/theme/index.css +0 -352
- package/dist/theme/index.css.map +1 -1
- package/dist/theme/index.d.cts +1 -25
- package/dist/theme/index.d.ts +1 -25
- package/dist/theme/index.js +2 -2
- package/dist/web/client/index.cjs +10 -10
- package/dist/web/client/index.css +118 -0
- package/dist/web/client/index.css.map +1 -1
- package/dist/web/client/index.js +2 -2
- package/dist/web/index.cjs +10 -10
- package/dist/web/index.css +118 -0
- package/dist/web/index.css.map +1 -1
- package/dist/web/index.js +2 -2
- package/package.json +10 -4
- package/dist/AppearancePanel-UT57J69V.d.cts +0 -51
- package/dist/AppearancePanel-UT57J69V.d.ts +0 -51
- package/dist/ExportMenu-A2TLFiVv.d.cts +0 -311
- package/dist/ExportMenu-C8qck5AT.d.ts +0 -311
- package/dist/Select-BdZmK0Lt.d.cts +0 -66
- package/dist/Select-BdZmK0Lt.d.ts +0 -66
- package/dist/chart-types-BGVVO-zl.d.cts +0 -208
- package/dist/chart-types-BGVVO-zl.d.ts +0 -208
- package/dist/charts/index.css +0 -1167
- package/dist/charts/index.css.map +0 -1
- package/dist/chunk-3BAQDW3V.cjs +0 -1207
- package/dist/chunk-3BAQDW3V.cjs.map +0 -1
- package/dist/chunk-3NKRFUAR.js +0 -37
- package/dist/chunk-3NKRFUAR.js.map +0 -1
- package/dist/chunk-3TGSIILM.cjs +0 -201
- package/dist/chunk-3TGSIILM.cjs.map +0 -1
- package/dist/chunk-4GM5BGBN.cjs +0 -801
- package/dist/chunk-4GM5BGBN.cjs.map +0 -1
- package/dist/chunk-5LA3T22E.cjs +0 -562
- package/dist/chunk-5LA3T22E.cjs.map +0 -1
- package/dist/chunk-5SN66B2X.js +0 -2542
- package/dist/chunk-5SN66B2X.js.map +0 -1
- package/dist/chunk-6D22TFLA.cjs.map +0 -1
- package/dist/chunk-6H4DSTXR.js +0 -786
- package/dist/chunk-6H4DSTXR.js.map +0 -1
- package/dist/chunk-6HKQ5ILL.cjs +0 -1624
- package/dist/chunk-6HKQ5ILL.cjs.map +0 -1
- package/dist/chunk-7PX2AZ6Y.js +0 -39
- package/dist/chunk-7PX2AZ6Y.js.map +0 -1
- package/dist/chunk-7UZ5DETZ.js.map +0 -1
- package/dist/chunk-B6AVAX4F.js +0 -1415
- package/dist/chunk-B6AVAX4F.js.map +0 -1
- package/dist/chunk-BJZ2DKS5.cjs.map +0 -1
- package/dist/chunk-BUTQSDQH.js +0 -200
- package/dist/chunk-BUTQSDQH.js.map +0 -1
- package/dist/chunk-C2BCDNAV.js +0 -24
- package/dist/chunk-C2BCDNAV.js.map +0 -1
- package/dist/chunk-CJ2MKVAF.cjs +0 -46
- package/dist/chunk-CJ2MKVAF.cjs.map +0 -1
- package/dist/chunk-E7D6EKJ4.cjs +0 -44
- package/dist/chunk-E7D6EKJ4.cjs.map +0 -1
- package/dist/chunk-ECXBTUH6.cjs.map +0 -1
- package/dist/chunk-FAFAP4L5.js +0 -183
- package/dist/chunk-FAFAP4L5.js.map +0 -1
- package/dist/chunk-G2XGBO5V.cjs +0 -2565
- package/dist/chunk-G2XGBO5V.cjs.map +0 -1
- package/dist/chunk-GQELL2MF.cjs.map +0 -1
- package/dist/chunk-H2KQ3WSH.cjs.map +0 -1
- package/dist/chunk-H2Y6BSTL.cjs.map +0 -1
- package/dist/chunk-HCZW5AJN.cjs +0 -234
- package/dist/chunk-HCZW5AJN.cjs.map +0 -1
- package/dist/chunk-HDVAMYSG.js.map +0 -1
- package/dist/chunk-HN4PHABT.js +0 -126
- package/dist/chunk-HN4PHABT.js.map +0 -1
- package/dist/chunk-LTPTW2US.cjs.map +0 -1
- package/dist/chunk-MDB2WCRQ.cjs +0 -137
- package/dist/chunk-MDB2WCRQ.cjs.map +0 -1
- package/dist/chunk-MQRB634A.cjs +0 -34
- package/dist/chunk-MQRB634A.cjs.map +0 -1
- package/dist/chunk-NN3TUHIH.js +0 -28
- package/dist/chunk-NN3TUHIH.js.map +0 -1
- package/dist/chunk-OWS2KAXZ.js +0 -701
- package/dist/chunk-OWS2KAXZ.js.map +0 -1
- package/dist/chunk-P5BOFE5A.js.map +0 -1
- package/dist/chunk-PUPSK3DI.cjs +0 -216
- package/dist/chunk-PUPSK3DI.cjs.map +0 -1
- package/dist/chunk-Q2MFGYTE.cjs +0 -1449
- package/dist/chunk-Q2MFGYTE.cjs.map +0 -1
- package/dist/chunk-Q75DBVDY.cjs +0 -68
- package/dist/chunk-Q75DBVDY.cjs.map +0 -1
- package/dist/chunk-QALDZ7WQ.js.map +0 -1
- package/dist/chunk-QWE2RNCS.js +0 -1195
- package/dist/chunk-QWE2RNCS.js.map +0 -1
- package/dist/chunk-RQUFZAZ7.js +0 -1608
- package/dist/chunk-RQUFZAZ7.js.map +0 -1
- package/dist/chunk-SICKWUWB.js.map +0 -1
- package/dist/chunk-TCFC7XTB.js +0 -212
- package/dist/chunk-TCFC7XTB.js.map +0 -1
- package/dist/chunk-UTVXGAQP.cjs +0 -2437
- package/dist/chunk-UTVXGAQP.cjs.map +0 -1
- package/dist/chunk-UVEMY3FQ.cjs +0 -717
- package/dist/chunk-UVEMY3FQ.cjs.map +0 -1
- package/dist/chunk-VFJZQQZU.js.map +0 -1
- package/dist/chunk-WH7PYHZY.cjs +0 -35
- package/dist/chunk-WH7PYHZY.cjs.map +0 -1
- package/dist/chunk-WYH4TKS5.js.map +0 -1
- package/dist/chunk-XEX2AEZK.cjs.map +0 -1
- package/dist/chunk-XPTVHPCN.js +0 -2320
- package/dist/chunk-XPTVHPCN.js.map +0 -1
- package/dist/chunk-XWPDRMZG.js +0 -62
- package/dist/chunk-XWPDRMZG.js.map +0 -1
- package/dist/chunk-Y26OHHMX.js.map +0 -1
- package/dist/chunk-YJ6C3EKW.js.map +0 -1
- package/dist/motion-C651Ry6d.d.cts +0 -832
- package/dist/motion-C651Ry6d.d.ts +0 -832
- package/dist/report/index.css +0 -1239
- package/dist/report/index.css.map +0 -1
- /package/dist/{chunk-6UNG76Y2.js.map → chunk-2VVRZBUR.cjs.map} +0 -0
- /package/dist/{chunk-YTYDQBVY.cjs.map → chunk-K526GN7P.js.map} +0 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/theme/AppearanceProvider.tsx
|
|
7
|
+
|
|
8
|
+
// src/theme/ensure-readable-contrast.ts
|
|
9
|
+
function parseHex(hex) {
|
|
10
|
+
const clean = hex.replace(/^#/, "");
|
|
11
|
+
let r, g, b;
|
|
12
|
+
if (clean.length === 3) {
|
|
13
|
+
r = parseInt(clean[0] + clean[0], 16);
|
|
14
|
+
g = parseInt(clean[1] + clean[1], 16);
|
|
15
|
+
b = parseInt(clean[2] + clean[2], 16);
|
|
16
|
+
} else if (clean.length === 6) {
|
|
17
|
+
r = parseInt(clean.substring(0, 2), 16);
|
|
18
|
+
g = parseInt(clean.substring(2, 4), 16);
|
|
19
|
+
b = parseInt(clean.substring(4, 6), 16);
|
|
20
|
+
} else {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
|
|
24
|
+
return [r, g, b];
|
|
25
|
+
}
|
|
26
|
+
function toHex(r, g, b) {
|
|
27
|
+
const clamp = (n) => Math.max(0, Math.min(255, Math.round(n)));
|
|
28
|
+
return "#" + [clamp(r), clamp(g), clamp(b)].map((c) => c.toString(16).padStart(2, "0")).join("");
|
|
29
|
+
}
|
|
30
|
+
function rgbToHsl(r, g, b) {
|
|
31
|
+
const rn = r / 255;
|
|
32
|
+
const gn = g / 255;
|
|
33
|
+
const bn = b / 255;
|
|
34
|
+
const max = Math.max(rn, gn, bn);
|
|
35
|
+
const min = Math.min(rn, gn, bn);
|
|
36
|
+
const l = (max + min) / 2;
|
|
37
|
+
if (max === min) return [0, 0, l];
|
|
38
|
+
const d = max - min;
|
|
39
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
40
|
+
let h;
|
|
41
|
+
if (max === rn) h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6;
|
|
42
|
+
else if (max === gn) h = ((bn - rn) / d + 2) / 6;
|
|
43
|
+
else h = ((rn - gn) / d + 4) / 6;
|
|
44
|
+
return [h * 360, s, l];
|
|
45
|
+
}
|
|
46
|
+
function hslToRgb(h, s, l) {
|
|
47
|
+
const hNorm = (h % 360 + 360) % 360;
|
|
48
|
+
if (s === 0) {
|
|
49
|
+
const v = Math.round(l * 255);
|
|
50
|
+
return [v, v, v];
|
|
51
|
+
}
|
|
52
|
+
const hue2rgb = (p2, q2, t) => {
|
|
53
|
+
if (t < 0) t += 1;
|
|
54
|
+
if (t > 1) t -= 1;
|
|
55
|
+
if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
|
|
56
|
+
if (t < 1 / 2) return q2;
|
|
57
|
+
if (t < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - t) * 6;
|
|
58
|
+
return p2;
|
|
59
|
+
};
|
|
60
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
61
|
+
const p = 2 * l - q;
|
|
62
|
+
const hFrac = hNorm / 360;
|
|
63
|
+
return [
|
|
64
|
+
Math.round(hue2rgb(p, q, hFrac + 1 / 3) * 255),
|
|
65
|
+
Math.round(hue2rgb(p, q, hFrac) * 255),
|
|
66
|
+
Math.round(hue2rgb(p, q, hFrac - 1 / 3) * 255)
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
function relativeLuminance(r, g, b) {
|
|
70
|
+
const [rs, gs, bs] = [r / 255, g / 255, b / 255].map(
|
|
71
|
+
(c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
|
|
72
|
+
);
|
|
73
|
+
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
74
|
+
}
|
|
75
|
+
function contrastRatio(fg, bg) {
|
|
76
|
+
const l1 = relativeLuminance(...fg);
|
|
77
|
+
const l2 = relativeLuminance(...bg);
|
|
78
|
+
const lighter = Math.max(l1, l2);
|
|
79
|
+
const darker = Math.min(l1, l2);
|
|
80
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
81
|
+
}
|
|
82
|
+
var DEFAULT_MIN_RATIO = 4.5;
|
|
83
|
+
var MAX_ITERATIONS = 25;
|
|
84
|
+
function ensureReadableContrast(accentHex, surfaceHex, minRatio = DEFAULT_MIN_RATIO) {
|
|
85
|
+
const fg = parseHex(accentHex);
|
|
86
|
+
const bg = parseHex(surfaceHex);
|
|
87
|
+
if (!fg || !bg) return accentHex;
|
|
88
|
+
if (contrastRatio(fg, bg) >= minRatio) return accentHex;
|
|
89
|
+
const [h, s] = rgbToHsl(...fg);
|
|
90
|
+
const bgLuminance = relativeLuminance(...bg);
|
|
91
|
+
const shouldLighten = bgLuminance < 0.5;
|
|
92
|
+
const currentLightness = rgbToHsl(...fg)[2];
|
|
93
|
+
let lo, hi;
|
|
94
|
+
if (shouldLighten) {
|
|
95
|
+
lo = currentLightness;
|
|
96
|
+
hi = 1;
|
|
97
|
+
} else {
|
|
98
|
+
lo = 0;
|
|
99
|
+
hi = currentLightness;
|
|
100
|
+
}
|
|
101
|
+
let bestHex = accentHex;
|
|
102
|
+
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
|
103
|
+
const mid = (lo + hi) / 2;
|
|
104
|
+
const [rr, gg, bb] = hslToRgb(h, s, mid);
|
|
105
|
+
const ratio = contrastRatio([rr, gg, bb], bg);
|
|
106
|
+
if (ratio >= minRatio) {
|
|
107
|
+
bestHex = toHex(rr, gg, bb);
|
|
108
|
+
if (shouldLighten) hi = mid;
|
|
109
|
+
else lo = mid;
|
|
110
|
+
} else {
|
|
111
|
+
if (shouldLighten) lo = mid;
|
|
112
|
+
else hi = mid;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return bestHex;
|
|
116
|
+
}
|
|
117
|
+
var STORAGE_KEY = "void-appearance-settings";
|
|
118
|
+
var BRAND_PINK_HEX = {
|
|
119
|
+
default: "#f97316",
|
|
120
|
+
salesmind: "#ff005a"
|
|
121
|
+
};
|
|
122
|
+
var DARK_SURFACE_HEX = "#08040a";
|
|
123
|
+
var DEFAULT_SETTINGS = {
|
|
124
|
+
theme: "dark",
|
|
125
|
+
brand: "default",
|
|
126
|
+
navPlacement: "left",
|
|
127
|
+
density: "comfortable",
|
|
128
|
+
radius: "playful",
|
|
129
|
+
customColor: "#f97316"
|
|
130
|
+
// Default orange as fallback
|
|
131
|
+
};
|
|
132
|
+
function hexToRgb(hex) {
|
|
133
|
+
const cleanHex = hex.replace(/^#/, "");
|
|
134
|
+
const r = parseInt(cleanHex.substring(0, 2), 16);
|
|
135
|
+
const g = parseInt(cleanHex.substring(2, 4), 16);
|
|
136
|
+
const b = parseInt(cleanHex.substring(4, 6), 16);
|
|
137
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) {
|
|
138
|
+
return "249, 115, 22";
|
|
139
|
+
}
|
|
140
|
+
return `${r}, ${g}, ${b}`;
|
|
141
|
+
}
|
|
142
|
+
function relativeLuminance2(r, g, b) {
|
|
143
|
+
const [rs, gs, bs] = [r / 255, g / 255, b / 255].map(
|
|
144
|
+
(c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
|
|
145
|
+
);
|
|
146
|
+
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
147
|
+
}
|
|
148
|
+
function accentForegroundRgb(hex) {
|
|
149
|
+
const cleanHex = hex.replace(/^#/, "");
|
|
150
|
+
const r = parseInt(cleanHex.substring(0, 2), 16);
|
|
151
|
+
const g = parseInt(cleanHex.substring(2, 4), 16);
|
|
152
|
+
const b = parseInt(cleanHex.substring(4, 6), 16);
|
|
153
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) {
|
|
154
|
+
return "255, 255, 255";
|
|
155
|
+
}
|
|
156
|
+
return relativeLuminance2(r, g, b) > 0.179 ? "0, 0, 0" : "255, 255, 255";
|
|
157
|
+
}
|
|
158
|
+
function generateSecondaryColor(hex) {
|
|
159
|
+
const cleanHex = hex.replace(/^#/, "");
|
|
160
|
+
const r = parseInt(cleanHex.substring(0, 2), 16);
|
|
161
|
+
const g = parseInt(cleanHex.substring(2, 4), 16);
|
|
162
|
+
const b = parseInt(cleanHex.substring(4, 6), 16);
|
|
163
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) {
|
|
164
|
+
return "255, 208, 0";
|
|
165
|
+
}
|
|
166
|
+
const secondary = {
|
|
167
|
+
r: Math.min(255, g + 50),
|
|
168
|
+
g: Math.min(255, b + 100),
|
|
169
|
+
b: Math.min(255, r)
|
|
170
|
+
};
|
|
171
|
+
return `${secondary.r}, ${secondary.g}, ${secondary.b}`;
|
|
172
|
+
}
|
|
173
|
+
function loadSettings() {
|
|
174
|
+
if (typeof window === "undefined") return DEFAULT_SETTINGS;
|
|
175
|
+
try {
|
|
176
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
177
|
+
if (stored) {
|
|
178
|
+
const parsed = JSON.parse(stored);
|
|
179
|
+
return {
|
|
180
|
+
theme: parsed.theme || DEFAULT_SETTINGS.theme,
|
|
181
|
+
brand: parsed.brand || DEFAULT_SETTINGS.brand,
|
|
182
|
+
navPlacement: parsed.navPlacement || DEFAULT_SETTINGS.navPlacement,
|
|
183
|
+
density: parsed.density || DEFAULT_SETTINGS.density,
|
|
184
|
+
radius: parsed.radius || DEFAULT_SETTINGS.radius,
|
|
185
|
+
customColor: parsed.customColor || DEFAULT_SETTINGS.customColor
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
...DEFAULT_SETTINGS,
|
|
192
|
+
theme: "dark"
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function saveSettings(settings) {
|
|
196
|
+
if (typeof window === "undefined") return;
|
|
197
|
+
try {
|
|
198
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function applySettings(settings) {
|
|
203
|
+
if (typeof document === "undefined") return;
|
|
204
|
+
const root = document.documentElement;
|
|
205
|
+
root.setAttribute("data-theme", settings.theme);
|
|
206
|
+
root.setAttribute("data-brand", settings.brand);
|
|
207
|
+
root.setAttribute("data-nav", settings.navPlacement);
|
|
208
|
+
root.setAttribute("data-density", settings.density);
|
|
209
|
+
root.setAttribute("data-radius", settings.radius);
|
|
210
|
+
if (settings.brand === "custom") {
|
|
211
|
+
const primaryRgb = hexToRgb(settings.customColor);
|
|
212
|
+
const secondaryRgb = generateSecondaryColor(settings.customColor);
|
|
213
|
+
const foregroundRgb = accentForegroundRgb(settings.customColor);
|
|
214
|
+
root.style.setProperty("--custom-accent-rgb", primaryRgb);
|
|
215
|
+
root.style.setProperty("--custom-accent2-rgb", secondaryRgb);
|
|
216
|
+
root.style.setProperty("--custom-accent-fg-rgb", foregroundRgb);
|
|
217
|
+
} else {
|
|
218
|
+
root.style.removeProperty("--custom-accent-rgb");
|
|
219
|
+
root.style.removeProperty("--custom-accent2-rgb");
|
|
220
|
+
root.style.removeProperty("--custom-accent-fg-rgb");
|
|
221
|
+
}
|
|
222
|
+
const brandPinkHex = settings.brand === "custom" ? settings.customColor : BRAND_PINK_HEX[settings.brand] ?? BRAND_PINK_HEX.default;
|
|
223
|
+
root.style.setProperty(
|
|
224
|
+
"--brand-pink-readable",
|
|
225
|
+
ensureReadableContrast(brandPinkHex, DARK_SURFACE_HEX)
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
function prefersReducedMotion() {
|
|
229
|
+
if (typeof window === "undefined") return false;
|
|
230
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
231
|
+
}
|
|
232
|
+
var AppearanceContext = react.createContext(null);
|
|
233
|
+
function AppearanceProvider({
|
|
234
|
+
initialSettings,
|
|
235
|
+
disablePersistence = false,
|
|
236
|
+
children
|
|
237
|
+
}) {
|
|
238
|
+
const [settings, setSettings] = react.useState(() => ({
|
|
239
|
+
...DEFAULT_SETTINGS,
|
|
240
|
+
...initialSettings
|
|
241
|
+
}));
|
|
242
|
+
const [hydrated, setHydrated] = react.useState(false);
|
|
243
|
+
react.useEffect(() => {
|
|
244
|
+
if (!disablePersistence) {
|
|
245
|
+
const loaded = loadSettings();
|
|
246
|
+
setSettings({
|
|
247
|
+
...loaded,
|
|
248
|
+
// initialSettings still take priority over localStorage
|
|
249
|
+
...initialSettings
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
setHydrated(true);
|
|
253
|
+
}, []);
|
|
254
|
+
react.useEffect(() => {
|
|
255
|
+
applySettings(settings);
|
|
256
|
+
if (!disablePersistence && hydrated) {
|
|
257
|
+
saveSettings(settings);
|
|
258
|
+
}
|
|
259
|
+
}, [settings, disablePersistence, hydrated]);
|
|
260
|
+
react.useEffect(() => {
|
|
261
|
+
if (typeof window === "undefined") return;
|
|
262
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: light)");
|
|
263
|
+
const handleChange = (e) => {
|
|
264
|
+
setSettings((prev) => {
|
|
265
|
+
if (prev.theme === "light-contrast" || prev.theme === "dark-contrast") {
|
|
266
|
+
return prev;
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
...prev,
|
|
270
|
+
theme: e.matches ? "light" : "dark"
|
|
271
|
+
};
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
275
|
+
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
276
|
+
}, []);
|
|
277
|
+
react.useEffect(() => {
|
|
278
|
+
if (typeof window === "undefined") return;
|
|
279
|
+
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
280
|
+
const handleChange = () => {
|
|
281
|
+
applySettings(settings);
|
|
282
|
+
};
|
|
283
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
284
|
+
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
285
|
+
}, [settings]);
|
|
286
|
+
const setTheme = react.useCallback((theme) => {
|
|
287
|
+
setSettings((prev) => ({ ...prev, theme }));
|
|
288
|
+
}, []);
|
|
289
|
+
const setBrand = react.useCallback((brand) => {
|
|
290
|
+
setSettings((prev) => ({ ...prev, brand }));
|
|
291
|
+
}, []);
|
|
292
|
+
const setNavPlacement = react.useCallback((navPlacement) => {
|
|
293
|
+
setSettings((prev) => ({ ...prev, navPlacement }));
|
|
294
|
+
}, []);
|
|
295
|
+
const setDensity = react.useCallback((density) => {
|
|
296
|
+
setSettings((prev) => ({ ...prev, density }));
|
|
297
|
+
}, []);
|
|
298
|
+
const setRadius = react.useCallback((radius) => {
|
|
299
|
+
setSettings((prev) => ({ ...prev, radius }));
|
|
300
|
+
}, []);
|
|
301
|
+
const setCustomColor = react.useCallback((customColor) => {
|
|
302
|
+
setSettings((prev) => ({ ...prev, customColor, brand: "custom" }));
|
|
303
|
+
}, []);
|
|
304
|
+
const setAppearance = react.useCallback((partial) => {
|
|
305
|
+
setSettings((prev) => ({ ...prev, ...partial }));
|
|
306
|
+
}, []);
|
|
307
|
+
const resetToDefaults = react.useCallback(() => {
|
|
308
|
+
setSettings(DEFAULT_SETTINGS);
|
|
309
|
+
}, []);
|
|
310
|
+
const contextValue = {
|
|
311
|
+
...settings,
|
|
312
|
+
setTheme,
|
|
313
|
+
setBrand,
|
|
314
|
+
setNavPlacement,
|
|
315
|
+
setDensity,
|
|
316
|
+
setRadius,
|
|
317
|
+
setCustomColor,
|
|
318
|
+
setAppearance,
|
|
319
|
+
resetToDefaults
|
|
320
|
+
};
|
|
321
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AppearanceContext.Provider, { value: contextValue, children });
|
|
322
|
+
}
|
|
323
|
+
function useAppearance() {
|
|
324
|
+
const context = react.useContext(AppearanceContext);
|
|
325
|
+
if (!context) {
|
|
326
|
+
throw new Error("useAppearance must be used within an AppearanceProvider");
|
|
327
|
+
}
|
|
328
|
+
return context;
|
|
329
|
+
}
|
|
330
|
+
function initializeAppearance(settings) {
|
|
331
|
+
const loaded = loadSettings();
|
|
332
|
+
const merged = { ...loaded, ...settings };
|
|
333
|
+
applySettings(merged);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
exports.AppearanceProvider = AppearanceProvider;
|
|
337
|
+
exports.accentForegroundRgb = accentForegroundRgb;
|
|
338
|
+
exports.hexToRgb = hexToRgb;
|
|
339
|
+
exports.initializeAppearance = initializeAppearance;
|
|
340
|
+
exports.prefersReducedMotion = prefersReducedMotion;
|
|
341
|
+
exports.relativeLuminance = relativeLuminance2;
|
|
342
|
+
exports.useAppearance = useAppearance;
|
|
343
|
+
//# sourceMappingURL=out.js.map
|
|
344
|
+
//# sourceMappingURL=chunk-EPD4ZEPY.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/theme/AppearanceProvider.tsx","../src/theme/ensure-readable-contrast.ts"],"names":["p","q","relativeLuminance"],"mappings":"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","sourcesContent":["import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';\nimport { ensureReadableContrast } from './ensure-readable-contrast';\n\n/* ============================================================================\n APPEARANCE TYPES\n ============================================================================ */\n\nexport type Theme = 'light' | 'light-contrast' | 'dark' | 'dark-contrast';\nexport type Brand = 'default' | 'salesmind' | 'custom';\nexport type NavPlacement = 'left' | 'right' | 'bottom';\nexport type Density = 'comfortable' | 'compact';\nexport type Radius = 'playful' | 'sharp';\n\nexport interface AppearanceSettings {\n theme: Theme;\n brand: Brand;\n navPlacement: NavPlacement;\n density: Density;\n radius: Radius;\n /** Hex color for custom brand (e.g., \"#ff2d8a\") */\n customColor: string;\n}\n\nexport interface AppearanceContextValue extends AppearanceSettings {\n setTheme: (theme: Theme) => void;\n setBrand: (brand: Brand) => void;\n setNavPlacement: (nav: NavPlacement) => void;\n setDensity: (density: Density) => void;\n setRadius: (radius: Radius) => void;\n setCustomColor: (color: string) => void;\n setAppearance: (settings: Partial<AppearanceSettings>) => void;\n resetToDefaults: () => void;\n}\n\n/* ============================================================================\n CONSTANTS\n ============================================================================ */\n\nconst STORAGE_KEY = 'void-appearance-settings';\n\nconst BRAND_PINK_HEX: Record<string, string> = {\n default: '#f97316',\n salesmind: '#ff005a',\n};\n\nconst DARK_SURFACE_HEX = '#08040a';\n\nconst DEFAULT_SETTINGS: AppearanceSettings = {\n theme: 'dark',\n brand: 'default',\n navPlacement: 'left',\n density: 'comfortable',\n radius: 'playful',\n customColor: '#f97316', // Default orange as fallback\n};\n\n/* ============================================================================\n UTILITIES\n ============================================================================ */\n\n/** Detect system color scheme preference */\n\n\n/** Convert hex color to RGB triplet string (e.g., \"255, 45, 138\") */\nfunction hexToRgb(hex: string): string {\n // Remove # if present\n const cleanHex = hex.replace(/^#/, '');\n\n // Parse hex values\n const r = parseInt(cleanHex.substring(0, 2), 16);\n const g = parseInt(cleanHex.substring(2, 4), 16);\n const b = parseInt(cleanHex.substring(4, 6), 16);\n\n // Validate parsed values\n if (isNaN(r) || isNaN(g) || isNaN(b)) {\n // Fallback to default orange if parsing fails\n return '249, 115, 22';\n }\n\n return `${r}, ${g}, ${b}`;\n}\n\n/**\n * Compute relative luminance of an RGB triplet using the WCAG 2.1 formula.\n * Returns a value between 0 (black) and 1 (white).\n * @see https://www.w3.org/TR/WCAG21/#dfn-relative-luminance\n */\nfunction relativeLuminance(r: number, g: number, b: number): number {\n const [rs, gs, bs] = [r / 255, g / 255, b / 255].map((c) =>\n c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4),\n );\n return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;\n}\n\n/**\n * Determine whether text on an accent-colored background should be black or white.\n * Returns \"0, 0, 0\" for light accents and \"255, 255, 255\" for dark accents.\n * Uses WCAG 2.1 relative luminance with a 0.179 threshold (equivalent to ~4.5:1\n * contrast ratio against white).\n */\nfunction accentForegroundRgb(hex: string): string {\n const cleanHex = hex.replace(/^#/, '');\n const r = parseInt(cleanHex.substring(0, 2), 16);\n const g = parseInt(cleanHex.substring(2, 4), 16);\n const b = parseInt(cleanHex.substring(4, 6), 16);\n\n if (isNaN(r) || isNaN(g) || isNaN(b)) {\n return '255, 255, 255'; // Fallback: white text\n }\n\n return relativeLuminance(r, g, b) > 0.179 ? '0, 0, 0' : '255, 255, 255';\n}\n\n/** Generate a complementary/secondary color from primary */\nfunction generateSecondaryColor(hex: string): string {\n const cleanHex = hex.replace(/^#/, '');\n const r = parseInt(cleanHex.substring(0, 2), 16);\n const g = parseInt(cleanHex.substring(2, 4), 16);\n const b = parseInt(cleanHex.substring(4, 6), 16);\n\n if (isNaN(r) || isNaN(g) || isNaN(b)) {\n return '255, 208, 0'; // Default yellow\n }\n\n // Shift hue by ~60 degrees for a harmonious secondary\n // Simple approach: rotate RGB channels and boost brightness\n const secondary = {\n r: Math.min(255, g + 50),\n g: Math.min(255, b + 100),\n b: Math.min(255, r),\n };\n\n return `${secondary.r}, ${secondary.g}, ${secondary.b}`;\n}\n\n/** Load settings from localStorage */\nfunction loadSettings(): AppearanceSettings {\n if (typeof window === 'undefined') return DEFAULT_SETTINGS;\n\n try {\n const stored = localStorage.getItem(STORAGE_KEY);\n if (stored) {\n const parsed = JSON.parse(stored) as Partial<AppearanceSettings>;\n return {\n theme: parsed.theme || DEFAULT_SETTINGS.theme,\n brand: parsed.brand || DEFAULT_SETTINGS.brand,\n navPlacement: parsed.navPlacement || DEFAULT_SETTINGS.navPlacement,\n density: parsed.density || DEFAULT_SETTINGS.density,\n radius: parsed.radius || DEFAULT_SETTINGS.radius,\n customColor: parsed.customColor || DEFAULT_SETTINGS.customColor,\n };\n }\n } catch {\n // Ignore parse errors\n }\n\n // Use default dark preference for initial theme if no stored value\n return {\n ...DEFAULT_SETTINGS,\n theme: 'dark',\n };\n}\n\n/** Save settings to localStorage */\nfunction saveSettings(settings: AppearanceSettings): void {\n if (typeof window === 'undefined') return;\n\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));\n } catch {\n // Ignore storage errors (e.g., private browsing)\n }\n}\n\n/** Apply settings to document.documentElement */\nfunction applySettings(settings: AppearanceSettings): void {\n if (typeof document === 'undefined') return;\n\n const root = document.documentElement;\n\n // Apply data attributes\n root.setAttribute('data-theme', settings.theme);\n root.setAttribute('data-brand', settings.brand);\n root.setAttribute('data-nav', settings.navPlacement);\n root.setAttribute('data-density', settings.density);\n root.setAttribute('data-radius', settings.radius);\n\n // Handle custom brand RGB injection\n if (settings.brand === 'custom') {\n const primaryRgb = hexToRgb(settings.customColor);\n const secondaryRgb = generateSecondaryColor(settings.customColor);\n const foregroundRgb = accentForegroundRgb(settings.customColor);\n\n root.style.setProperty('--custom-accent-rgb', primaryRgb);\n root.style.setProperty('--custom-accent2-rgb', secondaryRgb);\n root.style.setProperty('--custom-accent-fg-rgb', foregroundRgb);\n } else {\n root.style.removeProperty('--custom-accent-rgb');\n root.style.removeProperty('--custom-accent2-rgb');\n root.style.removeProperty('--custom-accent-fg-rgb');\n }\n\n const brandPinkHex =\n settings.brand === 'custom'\n ? settings.customColor\n : BRAND_PINK_HEX[settings.brand] ?? BRAND_PINK_HEX.default;\n\n root.style.setProperty(\n '--brand-pink-readable',\n ensureReadableContrast(brandPinkHex, DARK_SURFACE_HEX),\n );\n}\n\n/** Check if user prefers reduced motion */\nfunction prefersReducedMotion(): boolean {\n if (typeof window === 'undefined') return false;\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/* ============================================================================\n CONTEXT\n ============================================================================ */\n\nconst AppearanceContext = createContext<AppearanceContextValue | null>(null);\n\n/* ============================================================================\n PROVIDER COMPONENT\n ============================================================================ */\n\nexport interface AppearanceProviderProps {\n /** Initial settings (overrides localStorage) */\n initialSettings?: Partial<AppearanceSettings>;\n /** Disable localStorage persistence */\n disablePersistence?: boolean;\n /** Children */\n children: React.ReactNode;\n}\n\nexport function AppearanceProvider({\n initialSettings,\n disablePersistence = false,\n children,\n}: AppearanceProviderProps) {\n // Start with deterministic defaults to avoid SSR/client hydration mismatch.\n // localStorage is read after mount in the effect below.\n const [settings, setSettings] = useState<AppearanceSettings>(() => ({\n ...DEFAULT_SETTINGS,\n ...initialSettings,\n }));\n\n // After hydration, sync with localStorage (runs once on mount)\n const [hydrated, setHydrated] = useState(false);\n useEffect(() => {\n if (!disablePersistence) {\n const loaded = loadSettings();\n setSettings({\n ...loaded,\n // initialSettings still take priority over localStorage\n ...initialSettings,\n });\n }\n setHydrated(true);\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Apply settings to DOM whenever they change\n useEffect(() => {\n applySettings(settings);\n if (!disablePersistence && hydrated) {\n saveSettings(settings);\n }\n }, [settings, disablePersistence, hydrated]);\n\n // Listen for system theme changes\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const mediaQuery = window.matchMedia('(prefers-color-scheme: light)');\n\n const handleChange = (e: MediaQueryListEvent) => {\n // Only auto-switch if user hasn't explicitly set a contrast variant\n setSettings((prev) => {\n if (prev.theme === 'light-contrast' || prev.theme === 'dark-contrast') {\n return prev; // Don't override contrast preferences\n }\n return {\n ...prev,\n theme: e.matches ? 'light' : 'dark',\n };\n });\n };\n\n mediaQuery.addEventListener('change', handleChange);\n return () => mediaQuery.removeEventListener('change', handleChange);\n }, []);\n\n // Listen for reduced motion preference changes\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');\n\n // Initial check is handled by CSS media queries in tokens.css\n // This effect ensures we respect the preference dynamically\n const handleChange = () => {\n // Re-apply settings to ensure any motion-dependent values are updated\n applySettings(settings);\n };\n\n mediaQuery.addEventListener('change', handleChange);\n return () => mediaQuery.removeEventListener('change', handleChange);\n }, [settings]);\n\n const setTheme = useCallback((theme: Theme) => {\n setSettings((prev) => ({ ...prev, theme }));\n }, []);\n\n const setBrand = useCallback((brand: Brand) => {\n setSettings((prev) => ({ ...prev, brand }));\n }, []);\n\n const setNavPlacement = useCallback((navPlacement: NavPlacement) => {\n setSettings((prev) => ({ ...prev, navPlacement }));\n }, []);\n\n const setDensity = useCallback((density: Density) => {\n setSettings((prev) => ({ ...prev, density }));\n }, []);\n\n const setRadius = useCallback((radius: Radius) => {\n setSettings((prev) => ({ ...prev, radius }));\n }, []);\n\n const setCustomColor = useCallback((customColor: string) => {\n setSettings((prev) => ({ ...prev, customColor, brand: 'custom' }));\n }, []);\n\n const setAppearance = useCallback((partial: Partial<AppearanceSettings>) => {\n setSettings((prev) => ({ ...prev, ...partial }));\n }, []);\n\n const resetToDefaults = useCallback(() => {\n setSettings(DEFAULT_SETTINGS);\n }, []);\n\n const contextValue: AppearanceContextValue = {\n ...settings,\n setTheme,\n setBrand,\n setNavPlacement,\n setDensity,\n setRadius,\n setCustomColor,\n setAppearance,\n resetToDefaults,\n };\n\n return <AppearanceContext.Provider value={contextValue}>{children}</AppearanceContext.Provider>;\n}\n\n/* ============================================================================\n HOOK\n ============================================================================ */\n\n// eslint-disable-next-line react-refresh/only-export-components\nexport function useAppearance(): AppearanceContextValue {\n const context = useContext(AppearanceContext);\n\n if (!context) {\n throw new Error('useAppearance must be used within an AppearanceProvider');\n }\n\n return context;\n}\n\n/* ============================================================================\n STANDALONE UTILITY (for use outside React, e.g., SSR or initial load)\n ============================================================================ */\n\n// eslint-disable-next-line react-refresh/only-export-components\nexport function initializeAppearance(settings?: Partial<AppearanceSettings>): void {\n const loaded = loadSettings();\n const merged = { ...loaded, ...settings };\n applySettings(merged);\n}\n\n/* ============================================================================\n UTILITY EXPORTS\n ============================================================================ */\n\n// eslint-disable-next-line react-refresh/only-export-components\nexport { hexToRgb, relativeLuminance, accentForegroundRgb, prefersReducedMotion };\n","/**\n * Derive a readable accent text color that meets WCAG AA contrast requirements.\n *\n * Given an accent color intended for small text (e.g., section eyebrow labels),\n * this module computes a lightness-adjusted variant that satisfies a minimum\n * contrast ratio against a given surface color. Hue and saturation are preserved\n * so the result stays recognisably \"on brand\".\n *\n * The WCAG math here intentionally matches `relativeLuminance` in\n * `AppearanceProvider.tsx` (same constants, same 0.03928 threshold) so that\n * contrast decisions are consistent across the design system.\n */\n\n/* ============================================================================\n COLOR PARSING\n ============================================================================ */\n\n/** Parse a 3- or 6-character hex string into an RGB triplet, or null on failure. */\nexport function parseHex(hex: string): [number, number, number] | null {\n const clean = hex.replace(/^#/, '');\n\n let r: number, g: number, b: number;\n\n if (clean.length === 3) {\n r = parseInt(clean[0] + clean[0], 16);\n g = parseInt(clean[1] + clean[1], 16);\n b = parseInt(clean[2] + clean[2], 16);\n } else if (clean.length === 6) {\n r = parseInt(clean.substring(0, 2), 16);\n g = parseInt(clean.substring(2, 4), 16);\n b = parseInt(clean.substring(4, 6), 16);\n } else {\n return null;\n }\n\n if (isNaN(r) || isNaN(g) || isNaN(b)) return null;\n\n return [r, g, b];\n}\n\n/** Format an RGB triplet as a 6-character hex string with `#` prefix. */\nexport function toHex(r: number, g: number, b: number): string {\n const clamp = (n: number) => Math.max(0, Math.min(255, Math.round(n)));\n return (\n '#' +\n [clamp(r), clamp(g), clamp(b)]\n .map((c) => c.toString(16).padStart(2, '0'))\n .join('')\n );\n}\n\n/* ============================================================================\n HSL CONVERSION\n ============================================================================ */\n\n/** Convert an RGB triplet (0–255 each) to HSL (h: 0–360, s/l: 0–1). */\nexport function rgbToHsl(\n r: number,\n g: number,\n b: number,\n): [number, number, number] {\n const rn = r / 255;\n const gn = g / 255;\n const bn = b / 255;\n const max = Math.max(rn, gn, bn);\n const min = Math.min(rn, gn, bn);\n const l = (max + min) / 2;\n\n if (max === min) return [0, 0, l]; // achromatic\n\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n\n let h: number;\n if (max === rn) h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6;\n else if (max === gn) h = ((bn - rn) / d + 2) / 6;\n else h = ((rn - gn) / d + 4) / 6;\n\n return [h * 360, s, l];\n}\n\n/** Convert HSL (h: 0–360, s/l: 0–1) back to an RGB triplet (0–255 each). */\nexport function hslToRgb(\n h: number,\n s: number,\n l: number,\n): [number, number, number] {\n const hNorm = ((h % 360) + 360) % 360; // normalise negative hues\n\n if (s === 0) {\n const v = Math.round(l * 255);\n return [v, v, v]; // achromatic\n }\n\n const hue2rgb = (p: number, q: number, t: number) => {\n if (t < 0) t += 1;\n if (t > 1) t -= 1;\n if (t < 1 / 6) return p + (q - p) * 6 * t;\n if (t < 1 / 2) return q;\n if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;\n return p;\n };\n\n const q = l < 0.5 ? l * (1 + s) : l + s - l * s;\n const p = 2 * l - q;\n const hFrac = hNorm / 360;\n\n return [\n Math.round(hue2rgb(p, q, hFrac + 1 / 3) * 255),\n Math.round(hue2rgb(p, q, hFrac) * 255),\n Math.round(hue2rgb(p, q, hFrac - 1 / 3) * 255),\n ];\n}\n\n/* ============================================================================\n WCAG CONTRAST\n ============================================================================ */\n\n/**\n * Compute relative luminance using the WCAG 2.1 formula.\n *\n * Intentionally identical to the implementation in AppearanceProvider.tsx\n * so that all contrast decisions in the DS stay consistent.\n *\n * @see https://www.w3.org/TR/WCAG21/#dfn-relative-luminance\n */\nexport function relativeLuminance(r: number, g: number, b: number): number {\n const [rs, gs, bs] = [r / 255, g / 255, b / 255].map((c) =>\n c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4),\n );\n return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;\n}\n\n/**\n * WCAG 2.1 contrast ratio between two RGB triplets.\n * Result ranges from 1 (identical) to 21 (black vs white).\n */\nexport function contrastRatio(\n fg: [number, number, number],\n bg: [number, number, number],\n): number {\n const l1 = relativeLuminance(...fg);\n const l2 = relativeLuminance(...bg);\n const lighter = Math.max(l1, l2);\n const darker = Math.min(l1, l2);\n return (lighter + 0.05) / (darker + 0.05);\n}\n\n/* ============================================================================\n MAIN: DERIVE READABLE ACCENT COLOR\n ============================================================================ */\n\n/** Default minimum contrast ratio — WCAG AA for small text. */\nconst DEFAULT_MIN_RATIO = 4.5;\n\n/** Maximum binary-search iterations (convergence is fast; 25 is generous). */\nconst MAX_ITERATIONS = 25;\n\n/**\n * Return an accent color adjusted to meet a minimum contrast ratio against\n * the given surface color. The hue and saturation of the original color are\n * preserved; only lightness is increased (for dark surfaces) or decreased\n * (for light surfaces) until the target ratio is reached.\n *\n * If the original color already satisfies the ratio, it is returned as-is.\n * If the input is malformed, `fallbackHex` (default: the original input) is\n * returned so consumers degrade gracefully.\n *\n * @param accentHex Foreground accent color as a 3- or 6-char hex string\n * @param surfaceHex Background surface color to contrast against\n * @param minRatio Minimum WCAG contrast ratio (default 4.5 for AA small text)\n * @returns Adjusted hex color string\n */\nexport function ensureReadableContrast(\n accentHex: string,\n surfaceHex: string,\n minRatio = DEFAULT_MIN_RATIO,\n): string {\n const fg = parseHex(accentHex);\n const bg = parseHex(surfaceHex);\n\n // Guard: if either input is unparseable, return the original accent unchanged.\n if (!fg || !bg) return accentHex;\n\n // Fast path: already meets contrast — no adjustment needed.\n if (contrastRatio(fg, bg) >= minRatio) return accentHex;\n\n const [h, s] = rgbToHsl(...fg);\n const bgLuminance = relativeLuminance(...bg);\n\n // Determine adjustment direction:\n // On a dark surface (low luminance) we lighten; on a light surface we darken.\n const shouldLighten = bgLuminance < 0.5;\n\n const currentLightness = rgbToHsl(...fg)[2];\n let lo: number, hi: number;\n if (shouldLighten) {\n lo = currentLightness;\n hi = 1.0;\n } else {\n lo = 0;\n hi = currentLightness;\n }\n\n let bestHex = accentHex;\n\n for (let i = 0; i < MAX_ITERATIONS; i++) {\n const mid = (lo + hi) / 2;\n const [rr, gg, bb] = hslToRgb(h, s, mid);\n const ratio = contrastRatio([rr, gg, bb], bg);\n\n if (ratio >= minRatio) {\n bestHex = toHex(rr, gg, bb);\n if (shouldLighten) hi = mid;\n else lo = mid;\n } else {\n if (shouldLighten) lo = mid;\n else hi = mid;\n }\n }\n\n return bestHex;\n}\n"]}
|
|
@@ -1,42 +1,27 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var React = require('react');
|
|
5
|
-
var clsx2 = require('clsx');
|
|
6
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
3
|
+
var chunkEPD4ZEPY_cjs = require('./chunk-EPD4ZEPY.cjs');
|
|
7
4
|
var lucideReact = require('lucide-react');
|
|
5
|
+
var clsx = require('clsx');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
var React = require('react');
|
|
8
8
|
|
|
9
9
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
10
|
|
|
11
|
+
var clsx__default = /*#__PURE__*/_interopDefault(clsx);
|
|
11
12
|
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
12
|
-
var clsx2__default = /*#__PURE__*/_interopDefault(clsx2);
|
|
13
13
|
|
|
14
|
-
var VoidBackground = React__default.default.forwardRef(
|
|
15
|
-
({ showGrid = true, showGrain = true, className, children }, ref) => {
|
|
16
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: clsx2__default.default("void", className), children: [
|
|
17
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "void__base", "aria-hidden": "true" }),
|
|
18
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "void__orbs", "aria-hidden": "true", children: [
|
|
19
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "void__orb void__orb--warm" }),
|
|
20
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "void__orb void__orb--purple" })
|
|
21
|
-
] }),
|
|
22
|
-
showGrid && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "void__grid", "aria-hidden": "true" }),
|
|
23
|
-
showGrain && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "void__grain", "aria-hidden": "true" }),
|
|
24
|
-
children && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "void__content", children })
|
|
25
|
-
] });
|
|
26
|
-
}
|
|
27
|
-
);
|
|
28
|
-
VoidBackground.displayName = "VoidBackground";
|
|
29
14
|
var ThemeSelector = ({ className, style }) => {
|
|
30
|
-
const { theme, setTheme } =
|
|
15
|
+
const { theme, setTheme } = chunkEPD4ZEPY_cjs.useAppearance();
|
|
31
16
|
const handleThemeChange = (newTheme) => {
|
|
32
17
|
setTheme(newTheme);
|
|
33
18
|
};
|
|
34
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className:
|
|
19
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx__default.default("ds-theme-selector", className), style, role: "group", "aria-label": "Theme Selector", children: [
|
|
35
20
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
36
21
|
"button",
|
|
37
22
|
{
|
|
38
23
|
type: "button",
|
|
39
|
-
className:
|
|
24
|
+
className: clsx__default.default("ds-theme-selector__btn", { "ds-theme-selector__btn--active": theme === "light" }),
|
|
40
25
|
onClick: () => handleThemeChange("light"),
|
|
41
26
|
"aria-pressed": theme === "light",
|
|
42
27
|
children: [
|
|
@@ -49,7 +34,7 @@ var ThemeSelector = ({ className, style }) => {
|
|
|
49
34
|
"button",
|
|
50
35
|
{
|
|
51
36
|
type: "button",
|
|
52
|
-
className:
|
|
37
|
+
className: clsx__default.default("ds-theme-selector__btn", { "ds-theme-selector__btn--active": theme === "light-contrast" }),
|
|
53
38
|
onClick: () => handleThemeChange("light-contrast"),
|
|
54
39
|
"aria-pressed": theme === "light-contrast",
|
|
55
40
|
children: [
|
|
@@ -62,7 +47,7 @@ var ThemeSelector = ({ className, style }) => {
|
|
|
62
47
|
"button",
|
|
63
48
|
{
|
|
64
49
|
type: "button",
|
|
65
|
-
className:
|
|
50
|
+
className: clsx__default.default("ds-theme-selector__btn", { "ds-theme-selector__btn--active": theme === "dark" }),
|
|
66
51
|
onClick: () => handleThemeChange("dark"),
|
|
67
52
|
"aria-pressed": theme === "dark",
|
|
68
53
|
children: [
|
|
@@ -75,7 +60,7 @@ var ThemeSelector = ({ className, style }) => {
|
|
|
75
60
|
"button",
|
|
76
61
|
{
|
|
77
62
|
type: "button",
|
|
78
|
-
className:
|
|
63
|
+
className: clsx__default.default("ds-theme-selector__btn", { "ds-theme-selector__btn--active": theme === "dark-contrast" }),
|
|
79
64
|
onClick: () => handleThemeChange("dark-contrast"),
|
|
80
65
|
"aria-pressed": theme === "dark-contrast",
|
|
81
66
|
children: [
|
|
@@ -93,12 +78,12 @@ var BRAND_PRESETS = [
|
|
|
93
78
|
];
|
|
94
79
|
var ColorPicker = React__default.default.forwardRef(
|
|
95
80
|
({ className, style, hideCustom = false }, ref) => {
|
|
96
|
-
const { brand, setBrand, customColor, setCustomColor } =
|
|
81
|
+
const { brand, setBrand, customColor, setCustomColor } = chunkEPD4ZEPY_cjs.useAppearance();
|
|
97
82
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
98
83
|
"div",
|
|
99
84
|
{
|
|
100
85
|
ref,
|
|
101
|
-
className:
|
|
86
|
+
className: clsx__default.default("ds-color-picker", className),
|
|
102
87
|
style,
|
|
103
88
|
role: "group",
|
|
104
89
|
"aria-label": "Brand Color",
|
|
@@ -107,7 +92,7 @@ var ColorPicker = React__default.default.forwardRef(
|
|
|
107
92
|
"button",
|
|
108
93
|
{
|
|
109
94
|
type: "button",
|
|
110
|
-
className:
|
|
95
|
+
className: clsx__default.default("ds-color-picker__swatch", {
|
|
111
96
|
"ds-color-picker__swatch--active": brand === value
|
|
112
97
|
}),
|
|
113
98
|
onClick: () => setBrand(value),
|
|
@@ -127,7 +112,7 @@ var ColorPicker = React__default.default.forwardRef(
|
|
|
127
112
|
!hideCustom && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
128
113
|
"label",
|
|
129
114
|
{
|
|
130
|
-
className:
|
|
115
|
+
className: clsx__default.default("ds-color-picker__swatch", "ds-color-picker__swatch--custom", {
|
|
131
116
|
"ds-color-picker__swatch--active": brand === "custom"
|
|
132
117
|
}),
|
|
133
118
|
title: `Custom: ${customColor}`,
|
|
@@ -161,6 +146,5 @@ ColorPicker.displayName = "ColorPicker";
|
|
|
161
146
|
|
|
162
147
|
exports.ColorPicker = ColorPicker;
|
|
163
148
|
exports.ThemeSelector = ThemeSelector;
|
|
164
|
-
exports.VoidBackground = VoidBackground;
|
|
165
149
|
//# sourceMappingURL=out.js.map
|
|
166
|
-
//# sourceMappingURL=chunk-
|
|
150
|
+
//# sourceMappingURL=chunk-FVSL5YMB.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/ThemeSelector/ThemeSelector.tsx","../src/components/ColorPicker/ColorPicker.tsx"],"names":["clsx","jsx","jsxs"],"mappings":"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","sourcesContent":["import React from 'react';\nimport { Sun, Moon } from 'lucide-react';\nimport clsx from 'clsx';\nimport { useAppearance, Theme } from '../../theme/AppearanceProvider';\nimport './ThemeSelector.css';\n\nexport interface ThemeSelectorProps {\n className?: string;\n style?: React.CSSProperties;\n}\n\nexport const ThemeSelector: React.FC<ThemeSelectorProps> = ({ className, style }) => {\n const { theme, setTheme } = useAppearance();\n\n const handleThemeChange = (newTheme: Theme) => {\n setTheme(newTheme);\n };\n\n return (\n <div className={clsx('ds-theme-selector', className)} style={style} role=\"group\" aria-label=\"Theme Selector\">\n <button\n type=\"button\"\n className={clsx('ds-theme-selector__btn', { 'ds-theme-selector__btn--active': theme === 'light' })}\n onClick={() => handleThemeChange('light')}\n aria-pressed={theme === 'light'}\n >\n <Sun className=\"ds-theme-selector__icon\" />\n <span className=\"ds-theme-selector__label\">Light</span>\n </button>\n\n <button\n type=\"button\"\n className={clsx('ds-theme-selector__btn', { 'ds-theme-selector__btn--active': theme === 'light-contrast' })}\n onClick={() => handleThemeChange('light-contrast')}\n aria-pressed={theme === 'light-contrast'}\n >\n <Sun className=\"ds-theme-selector__icon\" />\n <span className=\"ds-theme-selector__label\">Light HC</span>\n </button>\n\n <button\n type=\"button\"\n className={clsx('ds-theme-selector__btn', { 'ds-theme-selector__btn--active': theme === 'dark' })}\n onClick={() => handleThemeChange('dark')}\n aria-pressed={theme === 'dark'}\n >\n <Moon className=\"ds-theme-selector__icon\" />\n <span className=\"ds-theme-selector__label\">Dark</span>\n </button>\n\n <button\n type=\"button\"\n className={clsx('ds-theme-selector__btn', { 'ds-theme-selector__btn--active': theme === 'dark-contrast' })}\n onClick={() => handleThemeChange('dark-contrast')}\n aria-pressed={theme === 'dark-contrast'}\n >\n <Moon className=\"ds-theme-selector__icon\" />\n <span className=\"ds-theme-selector__label\">Dark HC</span>\n </button>\n </div>\n );\n};\n\nThemeSelector.displayName = 'ThemeSelector';\n","import React from 'react';\nimport clsx from 'clsx';\nimport { useAppearance, Brand } from '../../theme/AppearanceProvider';\nimport './ColorPicker.css';\n\n/** Pre-configured brand presets shown as swatches. */\nconst BRAND_PRESETS: { value: Brand; label: string; color: string }[] = [\n { value: 'default', label: 'Warm Intelligence', color: '#f97316' },\n { value: 'salesmind', label: 'SalesMind', color: '#ff005a' },\n];\n\nexport interface ColorPickerProps {\n /** Additional CSS class name. */\n className?: string;\n /** Inline styles. */\n style?: React.CSSProperties;\n /**\n * Hide the custom color swatch and native color input.\n * When `true`, only the curated brand presets are shown.\n * Use this on production surfaces (e.g., marketing website footer)\n * where arbitrary custom colors would undermine brand consistency.\n * @default false\n */\n hideCustom?: boolean;\n}\n\n/**\n * Compact brand/accent color picker.\n * Renders preset brand swatches and an optional custom color input.\n * Consumes the `useAppearance()` context for state.\n */\nexport const ColorPicker = React.forwardRef<HTMLDivElement, ColorPickerProps>(\n ({ className, style, hideCustom = false }, ref) => {\n const { brand, setBrand, customColor, setCustomColor } = useAppearance();\n\n return (\n <div\n ref={ref}\n className={clsx('ds-color-picker', className)}\n style={style}\n role=\"group\"\n aria-label=\"Brand Color\"\n >\n {BRAND_PRESETS.map(({ value, label, color }) => (\n <button\n key={value}\n type=\"button\"\n className={clsx('ds-color-picker__swatch', {\n 'ds-color-picker__swatch--active': brand === value,\n })}\n onClick={() => setBrand(value)}\n aria-label={`Switch to ${label} color scheme`}\n aria-pressed={brand === value}\n title={label}\n >\n <span\n className=\"ds-color-picker__dot\"\n style={{ '--swatch-color': color } as React.CSSProperties}\n />\n </button>\n ))}\n\n {/* Custom color — clicking the dot opens the native color picker */}\n {!hideCustom && (\n <label\n className={clsx('ds-color-picker__swatch', 'ds-color-picker__swatch--custom', {\n 'ds-color-picker__swatch--active': brand === 'custom',\n })}\n title={`Custom: ${customColor}`}\n >\n <span\n className=\"ds-color-picker__dot\"\n style={{ '--swatch-color': customColor } as React.CSSProperties}\n />\n <input\n type=\"color\"\n value={customColor}\n onChange={(e) => setCustomColor(e.target.value)}\n className=\"ds-color-picker__native-input\"\n aria-label=\"Pick a custom accent color\"\n />\n </label>\n )}\n </div>\n );\n },\n);\n\nColorPicker.displayName = 'ColorPicker';\n"]}
|