@omerdlw/base-framework 1.0.6 → 2.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/dist/atoms/index.js +1 -103
- package/dist/atoms/index.js.map +1 -1
- package/dist/chunk-3BUP64Q6.js +105 -0
- package/dist/chunk-3BUP64Q6.js.map +1 -0
- package/dist/chunk-3MSK4XGK.js +9888 -0
- package/dist/chunk-3MSK4XGK.js.map +1 -0
- package/dist/chunk-4GMQKD6F.js +353 -0
- package/dist/chunk-4GMQKD6F.js.map +1 -0
- package/dist/chunk-65ZB4QTQ.js +888 -0
- package/dist/chunk-65ZB4QTQ.js.map +1 -0
- package/dist/chunk-6SG7V77J.js +577 -0
- package/dist/chunk-6SG7V77J.js.map +1 -0
- package/dist/chunk-7HQMXTNP.js +419 -0
- package/dist/chunk-7HQMXTNP.js.map +1 -0
- package/dist/chunk-7TAVYJ3A.js +371 -0
- package/dist/chunk-7TAVYJ3A.js.map +1 -0
- package/dist/chunk-AQ4BNXA2.js +349 -0
- package/dist/chunk-AQ4BNXA2.js.map +1 -0
- package/dist/chunk-CGHZSSFR.js +2166 -0
- package/dist/chunk-CGHZSSFR.js.map +1 -0
- package/dist/chunk-DGCQWQLZ.js +61 -0
- package/dist/chunk-DGCQWQLZ.js.map +1 -0
- package/dist/chunk-FIMPHODX.js +67 -0
- package/dist/chunk-FIMPHODX.js.map +1 -0
- package/dist/chunk-GF7TUX5I.js +1889 -0
- package/dist/chunk-GF7TUX5I.js.map +1 -0
- package/dist/chunk-JELG4JLT.js +454 -0
- package/dist/chunk-JELG4JLT.js.map +1 -0
- package/dist/chunk-K676GL7Z.js +51 -0
- package/dist/chunk-K676GL7Z.js.map +1 -0
- package/dist/chunk-KKQT46TP.js +26 -0
- package/dist/chunk-KKQT46TP.js.map +1 -0
- package/dist/chunk-QI3ALLIB.js +38 -0
- package/dist/chunk-QI3ALLIB.js.map +1 -0
- package/dist/chunk-QXR55VVS.js +1139 -0
- package/dist/chunk-QXR55VVS.js.map +1 -0
- package/dist/chunk-R2YGFJDJ.js +408 -0
- package/dist/chunk-R2YGFJDJ.js.map +1 -0
- package/dist/chunk-RFZQWLC5.js +27 -0
- package/dist/chunk-RFZQWLC5.js.map +1 -0
- package/dist/chunk-SYAVGTOX.js +521 -0
- package/dist/chunk-SYAVGTOX.js.map +1 -0
- package/dist/chunk-TV3WEF5G.js +784 -0
- package/dist/chunk-TV3WEF5G.js.map +1 -0
- package/dist/chunk-XASQT4CQ.js +280 -0
- package/dist/chunk-XASQT4CQ.js.map +1 -0
- package/dist/error/index.js +5 -682
- package/dist/error/index.js.map +1 -1
- package/dist/error/reporter.d.ts.map +1 -1
- package/dist/error/theme.d.ts +1 -1
- package/dist/error/theme.d.ts.map +1 -1
- package/dist/events.js +1 -7
- package/dist/events.js.map +1 -1
- package/dist/hooks/index.js +1 -358
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/use-global-event.d.ts +1 -1
- package/dist/index.js +18 -20029
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.js +2 -2035
- package/dist/kernel/index.js.map +1 -1
- package/dist/kernel/types.d.ts +1 -1
- package/dist/modules/ambient/analyze.d.ts +46 -0
- package/dist/modules/ambient/analyze.d.ts.map +1 -0
- package/dist/modules/ambient/constants.d.ts +1 -5
- package/dist/modules/ambient/constants.d.ts.map +1 -1
- package/dist/modules/ambient/context.d.ts.map +1 -1
- package/dist/modules/ambient/dom.d.ts +11 -12
- package/dist/modules/ambient/dom.d.ts.map +1 -1
- package/dist/modules/ambient/extractor.d.ts +10 -10
- package/dist/modules/ambient/extractor.d.ts.map +1 -1
- package/dist/modules/ambient/hooks.d.ts +1 -1
- package/dist/modules/ambient/hooks.d.ts.map +1 -1
- package/dist/modules/ambient/index.d.ts +6 -4
- package/dist/modules/ambient/index.d.ts.map +1 -1
- package/dist/modules/ambient/index.js +7 -762
- package/dist/modules/ambient/index.js.map +1 -1
- package/dist/modules/ambient/module.d.ts +1 -1
- package/dist/modules/ambient/module.d.ts.map +1 -1
- package/dist/modules/ambient/oklab.d.ts +36 -0
- package/dist/modules/ambient/oklab.d.ts.map +1 -0
- package/dist/modules/ambient/pipeline.d.ts +28 -0
- package/dist/modules/ambient/pipeline.d.ts.map +1 -0
- package/dist/modules/ambient/quantize.d.ts +22 -0
- package/dist/modules/ambient/quantize.d.ts.map +1 -0
- package/dist/modules/ambient/server/address.d.ts +8 -0
- package/dist/modules/ambient/server/address.d.ts.map +1 -0
- package/dist/modules/ambient/server/index.d.ts +53 -0
- package/dist/modules/ambient/server/index.d.ts.map +1 -0
- package/dist/modules/ambient/server/index.js +221 -0
- package/dist/modules/ambient/server/index.js.map +1 -0
- package/dist/modules/ambient/settings.d.ts +19 -0
- package/dist/modules/ambient/settings.d.ts.map +1 -0
- package/dist/modules/ambient/types.d.ts +105 -9
- package/dist/modules/ambient/types.d.ts.map +1 -1
- package/dist/modules/ambient/worker/index.d.ts +15 -0
- package/dist/modules/ambient/worker/index.d.ts.map +1 -0
- package/dist/modules/ambient/worker/index.js +114 -0
- package/dist/modules/ambient/worker/index.js.map +1 -0
- package/dist/modules/background/constants.d.ts +2 -2
- package/dist/modules/background/constants.d.ts.map +1 -1
- package/dist/modules/background/hooks.d.ts +1 -1
- package/dist/modules/background/index.js +6 -3078
- package/dist/modules/background/index.js.map +1 -1
- package/dist/modules/background/motion.d.ts +9 -7
- package/dist/modules/background/motion.d.ts.map +1 -1
- package/dist/modules/background/overlay.d.ts +3 -8
- package/dist/modules/background/overlay.d.ts.map +1 -1
- package/dist/modules/background/types.d.ts +16 -2
- package/dist/modules/background/types.d.ts.map +1 -1
- package/dist/modules/context-menu/constants.d.ts +2 -2
- package/dist/modules/context-menu/constants.d.ts.map +1 -1
- package/dist/modules/context-menu/hooks.d.ts +1 -1
- package/dist/modules/context-menu/index.js +10 -1896
- package/dist/modules/context-menu/index.js.map +1 -1
- package/dist/modules/context-menu/motion.d.ts +1 -8
- package/dist/modules/context-menu/motion.d.ts.map +1 -1
- package/dist/modules/context-menu/overlay.d.ts.map +1 -1
- package/dist/modules/context-menu/types.d.ts +16 -2
- package/dist/modules/context-menu/types.d.ts.map +1 -1
- package/dist/modules/controls/constants.d.ts +1 -1
- package/dist/modules/controls/constants.d.ts.map +1 -1
- package/dist/modules/controls/entries.d.ts +1 -1
- package/dist/modules/controls/hooks.d.ts +1 -1
- package/dist/modules/controls/index.js +4 -1110
- package/dist/modules/controls/index.js.map +1 -1
- package/dist/modules/controls/overlay.d.ts +1 -1
- package/dist/modules/controls/types.d.ts +2 -2
- package/dist/modules/dock/cards.d.ts +708 -24
- package/dist/modules/dock/cards.d.ts.map +1 -1
- package/dist/modules/dock/constants.d.ts +2 -2
- package/dist/modules/dock/constants.d.ts.map +1 -1
- package/dist/modules/dock/helpers.d.ts +2 -2
- package/dist/modules/dock/hooks.d.ts +2 -2
- package/dist/modules/dock/index.d.ts +1 -1
- package/dist/modules/dock/index.d.ts.map +1 -1
- package/dist/modules/dock/index.js +9 -12132
- package/dist/modules/dock/index.js.map +1 -1
- package/dist/modules/dock/module.d.ts +1 -1
- package/dist/modules/dock/motion.d.ts +41 -915
- package/dist/modules/dock/motion.d.ts.map +1 -1
- package/dist/modules/dock/overlay.d.ts.map +1 -1
- package/dist/modules/dock/runtime/dock.d.ts +1 -23
- package/dist/modules/dock/runtime/dock.d.ts.map +1 -1
- package/dist/modules/dock/styles.d.ts +4 -4
- package/dist/modules/dock/surface/hooks.d.ts +5 -20
- package/dist/modules/dock/surface/hooks.d.ts.map +1 -1
- package/dist/modules/dock/surface/machine.d.ts +4 -3
- package/dist/modules/dock/surface/machine.d.ts.map +1 -1
- package/dist/modules/dock/surface/stack.d.ts.map +1 -1
- package/dist/modules/dock/types.d.ts +158 -3
- package/dist/modules/dock/types.d.ts.map +1 -1
- package/dist/modules/index.js +16 -17883
- package/dist/modules/index.js.map +1 -1
- package/dist/modules/loading/constants.d.ts +1 -1
- package/dist/modules/loading/constants.d.ts.map +1 -1
- package/dist/modules/loading/context.d.ts +1 -1
- package/dist/modules/loading/hooks.d.ts +1 -1
- package/dist/modules/loading/index.js +5 -1153
- package/dist/modules/loading/index.js.map +1 -1
- package/dist/modules/loading/types.d.ts +2 -2
- package/dist/modules/media/context.d.ts +1 -1
- package/dist/modules/media/index.js +3 -570
- package/dist/modules/media/index.js.map +1 -1
- package/dist/modules/media/types.d.ts +2 -2
- package/dist/modules/modal/constants.d.ts +2 -2
- package/dist/modules/modal/constants.d.ts.map +1 -1
- package/dist/modules/modal/context.d.ts +1 -1
- package/dist/modules/modal/hooks.d.ts +3 -3
- package/dist/modules/modal/index.js +8 -2355
- package/dist/modules/modal/index.js.map +1 -1
- package/dist/modules/modal/motion.d.ts +3 -8
- package/dist/modules/modal/motion.d.ts.map +1 -1
- package/dist/modules/modal/overlay.d.ts.map +1 -1
- package/dist/modules/modal/types.d.ts +21 -0
- package/dist/modules/modal/types.d.ts.map +1 -1
- package/dist/modules/notification/constants.d.ts +2 -2
- package/dist/modules/notification/constants.d.ts.map +1 -1
- package/dist/modules/notification/context.d.ts +1 -1
- package/dist/modules/notification/hooks.d.ts +1 -1
- package/dist/modules/notification/index.js +4 -828
- package/dist/modules/notification/index.js.map +1 -1
- package/dist/modules/notification/motion.d.ts +1 -7
- package/dist/modules/notification/motion.d.ts.map +1 -1
- package/dist/modules/notification/overlay.d.ts.map +1 -1
- package/dist/modules/notification/types.d.ts +9 -1
- package/dist/modules/notification/types.d.ts.map +1 -1
- package/dist/overlay-TZ6L52A5.js +9 -0
- package/dist/overlay-TZ6L52A5.js.map +1 -0
- package/dist/provider.d.ts +1 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +6 -1968
- package/dist/provider.js.map +1 -1
- package/dist/result.js +2 -63
- package/dist/result.js.map +1 -1
- package/dist/theme.d.ts +45 -8
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +3 -41
- package/dist/theme.js.map +1 -1
- package/dist/tokens/index.d.ts +1 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +126 -155
- package/dist/tokens/index.js.map +1 -1
- package/dist/tokens/motion-config.d.ts +44 -0
- package/dist/tokens/motion-config.d.ts.map +1 -0
- package/dist/tokens/motion.d.ts +23 -174
- package/dist/tokens/motion.d.ts.map +1 -1
- package/dist/utils/index.js +4 -82
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/report.d.ts.map +1 -1
- package/docs/README.md +1 -0
- package/docs/architecture-and-rules.md +4 -3
- package/docs/core-engine.md +7 -10
- package/docs/getting-started.md +4 -2
- package/docs/modules/ambient.md +194 -80
- package/docs/modules/background.md +3 -3
- package/docs/modules/context-menu.md +2 -2
- package/docs/modules/dock.md +8 -5
- package/docs/modules/modal.md +2 -2
- package/docs/modules/notification.md +2 -2
- package/docs/theming.md +81 -9
- package/package.json +22 -5
- package/templates/AGENTS.md +1 -1
- package/templates/motion/background.ts +17 -0
- package/templates/motion/context-menu.ts +63 -0
- package/templates/motion/dock.ts +327 -0
- package/templates/motion/modal.ts +79 -0
- package/templates/motion/notification.ts +27 -0
- package/templates/motion/tokens.ts +47 -0
- package/templates/tsconfig.json +7 -0
- package/dist/modules/ambient/color.d.ts +0 -21
- package/dist/modules/ambient/color.d.ts.map +0 -1
|
@@ -0,0 +1,1139 @@
|
|
|
1
|
+
import { useOptionalBackgroundState } from './chunk-CGHZSSFR.js';
|
|
2
|
+
import { defineThemeSpec, useTheme } from './chunk-7TAVYJ3A.js';
|
|
3
|
+
import { getOrCreateGlobalContext, defineModule } from './chunk-GF7TUX5I.js';
|
|
4
|
+
import { createStore, shallowEqual, useRequiredContext, useStore } from './chunk-TV3WEF5G.js';
|
|
5
|
+
import { useMemo, useState, use, useEffect, useRef } from 'react';
|
|
6
|
+
import { jsx } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
var AmbientContext = getOrCreateGlobalContext("AmbientContext", null);
|
|
9
|
+
function AmbientProvider({
|
|
10
|
+
children,
|
|
11
|
+
initialPalette = null
|
|
12
|
+
}) {
|
|
13
|
+
const [store] = useState(
|
|
14
|
+
() => createStore(
|
|
15
|
+
{ isExtracting: false, palette: initialPalette },
|
|
16
|
+
{ freezeSnapshots: false }
|
|
17
|
+
)
|
|
18
|
+
);
|
|
19
|
+
const value = useMemo(
|
|
20
|
+
() => ({
|
|
21
|
+
actions: {
|
|
22
|
+
setIsExtracting: (next) => store.publish((state) => {
|
|
23
|
+
const isExtracting = typeof next === "function" ? next(state.isExtracting) : next;
|
|
24
|
+
return isExtracting === state.isExtracting ? state : { ...state, isExtracting };
|
|
25
|
+
}),
|
|
26
|
+
setPalette: (next) => store.publish((state) => {
|
|
27
|
+
const palette = typeof next === "function" ? next(state.palette) : next;
|
|
28
|
+
return palette === state.palette ? state : { ...state, palette };
|
|
29
|
+
})
|
|
30
|
+
},
|
|
31
|
+
store
|
|
32
|
+
}),
|
|
33
|
+
[store]
|
|
34
|
+
);
|
|
35
|
+
return /* @__PURE__ */ jsx(AmbientContext, { value, children });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/modules/ambient/constants.ts
|
|
39
|
+
var ambientTheme = defineThemeSpec("ambient");
|
|
40
|
+
|
|
41
|
+
// src/modules/ambient/oklab.ts
|
|
42
|
+
var DEG = Math.PI / 180;
|
|
43
|
+
var LINEAR_LUT = new Float32Array(256);
|
|
44
|
+
for (let i = 0; i < 256; i++) {
|
|
45
|
+
const v = i / 255;
|
|
46
|
+
LINEAR_LUT[i] = v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
47
|
+
}
|
|
48
|
+
function clamp(value, min, max) {
|
|
49
|
+
return Math.max(min, Math.min(max, value));
|
|
50
|
+
}
|
|
51
|
+
function smoothstep(edge0, edge1, value) {
|
|
52
|
+
const t = clamp((value - edge0) / (edge1 - edge0), 0, 1);
|
|
53
|
+
return t * t * (3 - 2 * t);
|
|
54
|
+
}
|
|
55
|
+
function srgbToOklab(r, g, b) {
|
|
56
|
+
const lr = LINEAR_LUT[r & 255];
|
|
57
|
+
const lg = LINEAR_LUT[g & 255];
|
|
58
|
+
const lb = LINEAR_LUT[b & 255];
|
|
59
|
+
const l = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
|
|
60
|
+
const m = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
|
|
61
|
+
const s = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);
|
|
62
|
+
return [
|
|
63
|
+
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
64
|
+
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
65
|
+
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
function oklchToLinearSrgb(l, c, h) {
|
|
69
|
+
const a = c * Math.cos(h * DEG);
|
|
70
|
+
const b = c * Math.sin(h * DEG);
|
|
71
|
+
const l_ = (l + 0.3963377774 * a + 0.2158037573 * b) ** 3;
|
|
72
|
+
const m_ = (l - 0.1055613458 * a - 0.0638541728 * b) ** 3;
|
|
73
|
+
const s_ = (l - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
74
|
+
return [
|
|
75
|
+
4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_,
|
|
76
|
+
-1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_,
|
|
77
|
+
-0.0041960863 * l_ - 0.7034186147 * m_ + 1.707614701 * s_
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
function labToLch([l, a, b]) {
|
|
81
|
+
const c = Math.sqrt(a * a + b * b);
|
|
82
|
+
const h = c < 1e-6 ? 0 : (Math.atan2(b, a) / DEG + 360) % 360;
|
|
83
|
+
return { c, h, l };
|
|
84
|
+
}
|
|
85
|
+
var inGamut = (rgb) => rgb.every((v) => v >= -1e-5 && v <= 1 + 1e-5);
|
|
86
|
+
function maxChroma(l, h) {
|
|
87
|
+
if (l <= 0 || l >= 1) return 0;
|
|
88
|
+
let low = 0;
|
|
89
|
+
let high = 0.4;
|
|
90
|
+
for (let step = 0; step < 18; step++) {
|
|
91
|
+
const middle = (low + high) / 2;
|
|
92
|
+
if (inGamut(oklchToLinearSrgb(l, middle, h))) low = middle;
|
|
93
|
+
else high = middle;
|
|
94
|
+
}
|
|
95
|
+
return low;
|
|
96
|
+
}
|
|
97
|
+
function hueDistance(a, b) {
|
|
98
|
+
const d = Math.abs(a - b) % 360;
|
|
99
|
+
return d > 180 ? 360 - d : d;
|
|
100
|
+
}
|
|
101
|
+
function luminance({ c, h, l }) {
|
|
102
|
+
const [r, g, b] = oklchToLinearSrgb(l, c, h).map((v) => clamp(v, 0, 1));
|
|
103
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
104
|
+
}
|
|
105
|
+
function contrastRatio(a, b) {
|
|
106
|
+
const la = luminance(a);
|
|
107
|
+
const lb = luminance(b);
|
|
108
|
+
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
109
|
+
}
|
|
110
|
+
var round = (value, digits) => {
|
|
111
|
+
const factor = 10 ** digits;
|
|
112
|
+
return Math.round(value * factor) / factor;
|
|
113
|
+
};
|
|
114
|
+
function formatOklch({ c, h, l }) {
|
|
115
|
+
const chroma = c < 5e-4 ? 0 : c;
|
|
116
|
+
return `oklch(${round(clamp(l, 0, 1), 3).toFixed(3)} ${round(chroma, 3).toFixed(3)} ${round(chroma === 0 ? 0 : h, 1).toFixed(1)})`;
|
|
117
|
+
}
|
|
118
|
+
function toneOf(h, l, rel, cap = 0.3) {
|
|
119
|
+
const room = maxChroma(l, h) * 0.96;
|
|
120
|
+
return { c: Math.min(cap, clamp(rel, 0, 1) * room), h, l };
|
|
121
|
+
}
|
|
122
|
+
function onColorFor(background, minContrast = 4.5) {
|
|
123
|
+
const light = (l) => toneOf(background.h, l, 0.12, 0.012);
|
|
124
|
+
const dark = (l) => toneOf(background.h, l, 0.35, 0.03);
|
|
125
|
+
const towardLight = contrastRatio(light(0.985), background);
|
|
126
|
+
const towardDark = contrastRatio(dark(0.18), background);
|
|
127
|
+
const useLight = towardLight >= minContrast || towardLight >= towardDark;
|
|
128
|
+
for (let step = 0; step <= 12; step++) {
|
|
129
|
+
const candidate = useLight ? light(Math.min(1, 0.985 + step * 25e-4)) : dark(Math.max(0, 0.18 - step * 0.015));
|
|
130
|
+
if (contrastRatio(candidate, background) >= minContrast) return candidate;
|
|
131
|
+
}
|
|
132
|
+
return useLight ? { c: 0, h: 0, l: 1 } : { c: 0, h: 0, l: 0 };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/modules/ambient/settings.ts
|
|
136
|
+
var AMBIENT_TONES = Object.freeze([
|
|
137
|
+
10,
|
|
138
|
+
15,
|
|
139
|
+
20,
|
|
140
|
+
30,
|
|
141
|
+
40,
|
|
142
|
+
50,
|
|
143
|
+
60,
|
|
144
|
+
70,
|
|
145
|
+
80,
|
|
146
|
+
90,
|
|
147
|
+
95
|
|
148
|
+
]);
|
|
149
|
+
var AMBIENT_ROLES = Object.freeze([
|
|
150
|
+
"dominant",
|
|
151
|
+
"vibrant",
|
|
152
|
+
"muted",
|
|
153
|
+
"accent"
|
|
154
|
+
]);
|
|
155
|
+
var AMBIENT_REGIONS = Object.freeze([
|
|
156
|
+
"top",
|
|
157
|
+
"bottom",
|
|
158
|
+
"left",
|
|
159
|
+
"right",
|
|
160
|
+
"center"
|
|
161
|
+
]);
|
|
162
|
+
var DEFAULT_ANALYSIS = Object.freeze({
|
|
163
|
+
centerWeight: 0.5,
|
|
164
|
+
clusters: 7,
|
|
165
|
+
letterbox: true,
|
|
166
|
+
minContrast: 4.5,
|
|
167
|
+
minHueDistance: 40,
|
|
168
|
+
sampleSize: 128,
|
|
169
|
+
shadowLightness: 0.16
|
|
170
|
+
});
|
|
171
|
+
var DEFAULT_CSS_VAR_NAMES = Object.freeze({
|
|
172
|
+
on: "--ambient-on-{role}",
|
|
173
|
+
region: "--ambient-{region}",
|
|
174
|
+
role: "--ambient-{role}",
|
|
175
|
+
shadow: "--ambient-shadow",
|
|
176
|
+
tone: "--ambient-{role}-{tone}"
|
|
177
|
+
});
|
|
178
|
+
var DEFAULT_PROXY = "/api/ambient/proxy?url=";
|
|
179
|
+
var createDefaults = () => ({
|
|
180
|
+
analysis: { ...DEFAULT_ANALYSIS },
|
|
181
|
+
cacheLimit: 100,
|
|
182
|
+
cssVars: { ...DEFAULT_CSS_VAR_NAMES, tones: AMBIENT_TONES },
|
|
183
|
+
proxy: DEFAULT_PROXY,
|
|
184
|
+
strategy: "auto",
|
|
185
|
+
worker: null
|
|
186
|
+
});
|
|
187
|
+
var SETTINGS_KEY = /* @__PURE__ */ Symbol.for("@omerdlw/base-framework/ambient/settings");
|
|
188
|
+
var scope = globalThis;
|
|
189
|
+
function getAmbientSettings() {
|
|
190
|
+
return scope[SETTINGS_KEY] ??= createDefaults();
|
|
191
|
+
}
|
|
192
|
+
function mergeAmbientSettings(base, input) {
|
|
193
|
+
return {
|
|
194
|
+
analysis: { ...base.analysis, ...input.analysis },
|
|
195
|
+
cacheLimit: input.cacheLimit ?? base.cacheLimit,
|
|
196
|
+
cssVars: { ...base.cssVars, ...input.cssVars },
|
|
197
|
+
proxy: input.proxy === void 0 ? base.proxy : input.proxy,
|
|
198
|
+
strategy: input.strategy ?? base.strategy,
|
|
199
|
+
worker: input.worker === void 0 ? base.worker : input.worker
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function configureAmbient(input) {
|
|
203
|
+
const previous = getAmbientSettings();
|
|
204
|
+
const next = mergeAmbientSettings(previous, input);
|
|
205
|
+
scope[SETTINGS_KEY] = next;
|
|
206
|
+
return () => {
|
|
207
|
+
if (scope[SETTINGS_KEY] === next) scope[SETTINGS_KEY] = previous;
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function resolveAnalysis(overrides) {
|
|
211
|
+
return { ...getAmbientSettings().analysis, ...overrides };
|
|
212
|
+
}
|
|
213
|
+
function resolveProxyUrl(proxy, src) {
|
|
214
|
+
if (!proxy || !/^https?:\/\//i.test(src)) return null;
|
|
215
|
+
if (typeof proxy === "function") return proxy(src);
|
|
216
|
+
const encoded = encodeURIComponent(src);
|
|
217
|
+
return proxy.includes("{url}") ? proxy.replace("{url}", encoded) : `${proxy}${encoded}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/modules/ambient/quantize.ts
|
|
221
|
+
function kmeans(points, weights, requested, { iterations = 16, lightnessWeight = 0.45 } = {}) {
|
|
222
|
+
const n = weights.length;
|
|
223
|
+
const assignment = new Int32Array(n);
|
|
224
|
+
const k = Math.max(0, Math.min(requested, n));
|
|
225
|
+
const centroids = new Float64Array(k * 3);
|
|
226
|
+
if (k === 0) return { assignment, centroids, k };
|
|
227
|
+
const distance = (i, j) => {
|
|
228
|
+
const dl = points[i * 3] - centroids[j * 3];
|
|
229
|
+
const da = points[i * 3 + 1] - centroids[j * 3 + 1];
|
|
230
|
+
const db = points[i * 3 + 2] - centroids[j * 3 + 2];
|
|
231
|
+
return lightnessWeight * dl * dl + da * da + db * db;
|
|
232
|
+
};
|
|
233
|
+
let first = 0;
|
|
234
|
+
for (let i = 1; i < n; i++) if (weights[i] > weights[first]) first = i;
|
|
235
|
+
centroids.set(points.subarray(first * 3, first * 3 + 3), 0);
|
|
236
|
+
const nearest = new Float64Array(n).fill(Infinity);
|
|
237
|
+
let found = 1;
|
|
238
|
+
for (; found < k; found++) {
|
|
239
|
+
let best = -1;
|
|
240
|
+
let bestScore = 0;
|
|
241
|
+
for (let i = 0; i < n; i++) {
|
|
242
|
+
const d = distance(i, found - 1);
|
|
243
|
+
if (d < nearest[i]) nearest[i] = d;
|
|
244
|
+
const score = weights[i] * nearest[i];
|
|
245
|
+
if (score > bestScore) {
|
|
246
|
+
bestScore = score;
|
|
247
|
+
best = i;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (best < 0) break;
|
|
251
|
+
centroids.set(points.subarray(best * 3, best * 3 + 3), found * 3);
|
|
252
|
+
}
|
|
253
|
+
const sums = new Float64Array(found * 4);
|
|
254
|
+
for (let round2 = 0; round2 < iterations; round2++) {
|
|
255
|
+
let moved = round2 === 0;
|
|
256
|
+
for (let i = 0; i < n; i++) {
|
|
257
|
+
let best = 0;
|
|
258
|
+
let bestDistance = Infinity;
|
|
259
|
+
for (let j = 0; j < found; j++) {
|
|
260
|
+
const d = distance(i, j);
|
|
261
|
+
if (d < bestDistance) {
|
|
262
|
+
bestDistance = d;
|
|
263
|
+
best = j;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (assignment[i] !== best) {
|
|
267
|
+
assignment[i] = best;
|
|
268
|
+
moved = true;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (!moved) break;
|
|
272
|
+
sums.fill(0);
|
|
273
|
+
for (let i = 0; i < n; i++) {
|
|
274
|
+
const j = assignment[i] * 4;
|
|
275
|
+
const w = weights[i];
|
|
276
|
+
sums[j] += points[i * 3] * w;
|
|
277
|
+
sums[j + 1] += points[i * 3 + 1] * w;
|
|
278
|
+
sums[j + 2] += points[i * 3 + 2] * w;
|
|
279
|
+
sums[j + 3] += w;
|
|
280
|
+
}
|
|
281
|
+
for (let j = 0; j < found; j++) {
|
|
282
|
+
const w = sums[j * 4 + 3];
|
|
283
|
+
if (w <= 0) continue;
|
|
284
|
+
centroids[j * 3] = sums[j * 4] / w;
|
|
285
|
+
centroids[j * 3 + 1] = sums[j * 4 + 1] / w;
|
|
286
|
+
centroids[j * 3 + 2] = sums[j * 4 + 2] / w;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return { assignment, centroids: centroids.slice(0, found * 3), k: found };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/modules/ambient/analyze.ts
|
|
293
|
+
var MIN_CHROMA = 0.022;
|
|
294
|
+
var MIN_PRESENCE = 0.03;
|
|
295
|
+
var MIN_POPULATION = 4e-3;
|
|
296
|
+
var REGION_BITS = {
|
|
297
|
+
top: 1,
|
|
298
|
+
bottom: 2,
|
|
299
|
+
left: 4,
|
|
300
|
+
right: 8,
|
|
301
|
+
center: 16
|
|
302
|
+
};
|
|
303
|
+
var lightnessAt = (data, index) => srgbToOklab(data[index], data[index + 1], data[index + 2])[0];
|
|
304
|
+
function findContentBounds({ data, height, width }) {
|
|
305
|
+
const isBar = (count, at) => {
|
|
306
|
+
let sum = 0;
|
|
307
|
+
let max = 0;
|
|
308
|
+
let seen = 0;
|
|
309
|
+
for (let step = 0; step < count; step++) {
|
|
310
|
+
const index = at(step);
|
|
311
|
+
if (data[index + 3] < 128) continue;
|
|
312
|
+
const l = lightnessAt(data, index);
|
|
313
|
+
sum += l;
|
|
314
|
+
if (l > max) max = l;
|
|
315
|
+
seen++;
|
|
316
|
+
}
|
|
317
|
+
return seen > 0 && sum / seen < 0.12 && max < 0.2;
|
|
318
|
+
};
|
|
319
|
+
const row = (y) => isBar(width, (x) => (y * width + x) * 4);
|
|
320
|
+
const maxRows = Math.floor(height * 0.22);
|
|
321
|
+
const maxColumns = Math.floor(width * 0.22);
|
|
322
|
+
let top = 0;
|
|
323
|
+
while (top < maxRows && row(top)) top++;
|
|
324
|
+
let bottom = height;
|
|
325
|
+
while (height - bottom < maxRows && bottom - 1 > top && row(bottom - 1)) bottom--;
|
|
326
|
+
const column = (x) => isBar(bottom - top, (step) => ((top + step) * width + x) * 4);
|
|
327
|
+
let left = 0;
|
|
328
|
+
while (left < maxColumns && column(left)) left++;
|
|
329
|
+
let right = width;
|
|
330
|
+
while (width - right < maxColumns && right - 1 > left && column(right - 1)) right--;
|
|
331
|
+
return { bottom, left, right, top };
|
|
332
|
+
}
|
|
333
|
+
var reliability = (l) => smoothstep(0.1, 0.28, l) * (1 - smoothstep(0.9, 0.98, l));
|
|
334
|
+
var colorfulness = (c) => smoothstep(MIN_CHROMA, 0.06, c);
|
|
335
|
+
var isChromatic = (cluster) => cluster.c >= MIN_CHROMA && cluster.rely > 0;
|
|
336
|
+
function pickBest(items, score) {
|
|
337
|
+
let best = null;
|
|
338
|
+
let bestScore = 0;
|
|
339
|
+
for (const item of items) {
|
|
340
|
+
const value = score(item);
|
|
341
|
+
if (value > bestScore) {
|
|
342
|
+
bestScore = value;
|
|
343
|
+
best = item;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return best;
|
|
347
|
+
}
|
|
348
|
+
var round3 = (value) => Math.round(value * 1e3) / 1e3;
|
|
349
|
+
function buildSwatch(source, { cap, max, min, rel }, fallback, fromImage, minContrast) {
|
|
350
|
+
const h = source?.h ?? fallback.h;
|
|
351
|
+
const l = clamp(source?.l ?? fallback.l, min, max);
|
|
352
|
+
const tone = toneOf(h, l, rel, cap);
|
|
353
|
+
const ramp = {};
|
|
354
|
+
for (const step of AMBIENT_TONES) {
|
|
355
|
+
ramp[step] = formatOklch(toneOf(h, step / 100, rel, cap));
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
c: round3(tone.c),
|
|
359
|
+
color: formatOklch(tone),
|
|
360
|
+
fromImage,
|
|
361
|
+
h: Math.round(h * 10) / 10,
|
|
362
|
+
l: round3(tone.l),
|
|
363
|
+
on: formatOklch(onColorFor(tone, minContrast)),
|
|
364
|
+
population: round3(source?.pop ?? 0),
|
|
365
|
+
ramp
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
var lift = (rel, base, gain, ceiling) => clamp(base + gain * rel, 0, ceiling);
|
|
369
|
+
function clusterAmbientPixels(pixels, options) {
|
|
370
|
+
const { data, width, height } = pixels;
|
|
371
|
+
const bounds = options.letterbox ? findContentBounds(pixels) : { bottom: height, left: 0, right: width, top: 0 };
|
|
372
|
+
const boxWidth = Math.max(1, bounds.right - bounds.left);
|
|
373
|
+
const boxHeight = Math.max(1, bounds.bottom - bounds.top);
|
|
374
|
+
const binOf = new Int32Array(32768).fill(-1);
|
|
375
|
+
const binSums = [];
|
|
376
|
+
const pixelBins = new Int32Array(boxWidth * boxHeight).fill(-1);
|
|
377
|
+
const pixelRegions = new Uint8Array(boxWidth * boxHeight);
|
|
378
|
+
const sigma2 = 2 * 0.35 * 0.35;
|
|
379
|
+
let total = 0;
|
|
380
|
+
let sumL = 0;
|
|
381
|
+
for (let y = bounds.top; y < bounds.bottom; y++) {
|
|
382
|
+
const v = (y - bounds.top + 0.5) / boxHeight;
|
|
383
|
+
for (let x = bounds.left; x < bounds.right; x++) {
|
|
384
|
+
const index = (y * width + x) * 4;
|
|
385
|
+
if (data[index + 3] < 128) continue;
|
|
386
|
+
const r = data[index];
|
|
387
|
+
const g = data[index + 1];
|
|
388
|
+
const b = data[index + 2];
|
|
389
|
+
const [l, a, bb] = srgbToOklab(r, g, b);
|
|
390
|
+
const u = (x - bounds.left + 0.5) / boxWidth;
|
|
391
|
+
const du = (u - 0.5) * 2;
|
|
392
|
+
const dv = (v - 0.5) * 2;
|
|
393
|
+
const weight = 1 - options.centerWeight + 2 * options.centerWeight * Math.exp(-(du * du + dv * dv) / sigma2);
|
|
394
|
+
const key = r >> 3 << 10 | g >> 3 << 5 | b >> 3;
|
|
395
|
+
let bin = binOf[key];
|
|
396
|
+
if (bin < 0) {
|
|
397
|
+
bin = binSums.length / 4;
|
|
398
|
+
binOf[key] = bin;
|
|
399
|
+
binSums.push(0, 0, 0, 0);
|
|
400
|
+
}
|
|
401
|
+
binSums[bin * 4] += l * weight;
|
|
402
|
+
binSums[bin * 4 + 1] += a * weight;
|
|
403
|
+
binSums[bin * 4 + 2] += bb * weight;
|
|
404
|
+
binSums[bin * 4 + 3] += weight;
|
|
405
|
+
total += weight;
|
|
406
|
+
sumL += l * weight;
|
|
407
|
+
const local = (y - bounds.top) * boxWidth + (x - bounds.left);
|
|
408
|
+
pixelBins[local] = bin;
|
|
409
|
+
pixelRegions[local] = (v < 1 / 3 ? REGION_BITS.top : 0) | (v > 2 / 3 ? REGION_BITS.bottom : 0) | (u < 1 / 3 ? REGION_BITS.left : 0) | (u > 2 / 3 ? REGION_BITS.right : 0) | (Math.abs(u - 0.5) < 0.25 && Math.abs(v - 0.5) < 0.25 ? REGION_BITS.center : 0);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const meanL = total > 0 ? sumL / total : 0.5;
|
|
413
|
+
const bins = binSums.length / 4;
|
|
414
|
+
const groups = [[], []];
|
|
415
|
+
for (let i = 0; i < bins; i++) {
|
|
416
|
+
const w = binSums[i * 4 + 3];
|
|
417
|
+
const a = binSums[i * 4 + 1] / w;
|
|
418
|
+
const b = binSums[i * 4 + 2] / w;
|
|
419
|
+
const coloured = Math.sqrt(a * a + b * b) >= MIN_CHROMA && binSums[i * 4] / w >= 0.06;
|
|
420
|
+
groups[coloured ? 0 : 1].push(i);
|
|
421
|
+
}
|
|
422
|
+
const requested = clamp(Math.round(options.clusters), 2, 16);
|
|
423
|
+
const greyClusters = groups[0].length === 0 ? requested : groups[1].length === 0 ? 0 : requested >= 8 ? 3 : 2;
|
|
424
|
+
const budgets = [requested - greyClusters, greyClusters];
|
|
425
|
+
const clusters = [];
|
|
426
|
+
const clusterOfBin = new Int32Array(bins).fill(-1);
|
|
427
|
+
groups.forEach((members, group) => {
|
|
428
|
+
if (members.length === 0 || budgets[group] <= 0) return;
|
|
429
|
+
const points = new Float64Array(members.length * 3);
|
|
430
|
+
const weights = new Float64Array(members.length);
|
|
431
|
+
members.forEach((bin, i) => {
|
|
432
|
+
const w = binSums[bin * 4 + 3];
|
|
433
|
+
const a = binSums[bin * 4 + 1] / w;
|
|
434
|
+
const b = binSums[bin * 4 + 2] / w;
|
|
435
|
+
points[i * 3] = binSums[bin * 4] / w;
|
|
436
|
+
points[i * 3 + 1] = a;
|
|
437
|
+
points[i * 3 + 2] = b;
|
|
438
|
+
weights[i] = w * (1 + 5 * Math.sqrt(a * a + b * b));
|
|
439
|
+
});
|
|
440
|
+
const { assignment, centroids, k } = kmeans(points, weights, budgets[group]);
|
|
441
|
+
const pop = new Float64Array(k);
|
|
442
|
+
members.forEach((bin, i) => pop[assignment[i]] += binSums[bin * 4 + 3]);
|
|
443
|
+
const index = new Int32Array(k).fill(-1);
|
|
444
|
+
for (let j = 0; j < k; j++) {
|
|
445
|
+
if (pop[j] <= 0) continue;
|
|
446
|
+
const lch = labToLch([centroids[j * 3], centroids[j * 3 + 1], centroids[j * 3 + 2]]);
|
|
447
|
+
const room = maxChroma(lch.l, lch.h);
|
|
448
|
+
index[j] = clusters.length;
|
|
449
|
+
clusters.push({
|
|
450
|
+
...lch,
|
|
451
|
+
pop: pop[j] / total,
|
|
452
|
+
rel: room > 0 ? clamp(lch.c / room, 0, 1) : 0,
|
|
453
|
+
rely: reliability(lch.l)
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
members.forEach((bin, i) => clusterOfBin[bin] = index[assignment[i]]);
|
|
457
|
+
});
|
|
458
|
+
const regionWeights = AMBIENT_REGIONS.map(() => new Float64Array(clusters.length));
|
|
459
|
+
for (let i = 0; i < pixelBins.length; i++) {
|
|
460
|
+
const bin = pixelBins[i];
|
|
461
|
+
if (bin < 0) continue;
|
|
462
|
+
const cluster = clusterOfBin[bin];
|
|
463
|
+
const bits = pixelRegions[i];
|
|
464
|
+
AMBIENT_REGIONS.forEach((region, r) => {
|
|
465
|
+
if (bits & REGION_BITS[region]) regionWeights[r][cluster] += 1;
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
return { clusters, meanL, regionWeights };
|
|
469
|
+
}
|
|
470
|
+
function analyzeAmbientPixels(pixels, overrides = {}) {
|
|
471
|
+
const options = { ...DEFAULT_ANALYSIS, ...overrides };
|
|
472
|
+
const { clusters, meanL, regionWeights } = clusterAmbientPixels(pixels, options);
|
|
473
|
+
const chromatic = clusters.filter(isChromatic);
|
|
474
|
+
const presence = chromatic.reduce(
|
|
475
|
+
(sum, cluster) => sum + cluster.pop * colorfulness(cluster.c) * cluster.rely,
|
|
476
|
+
0
|
|
477
|
+
);
|
|
478
|
+
const neutral = presence < MIN_PRESENCE;
|
|
479
|
+
const dilution = 0.55 + 0.45 * Math.min(1, presence / 0.25);
|
|
480
|
+
const { minContrast, minHueDistance } = options;
|
|
481
|
+
const grey = { h: 0, l: clamp(meanL, 0.3, 0.8) };
|
|
482
|
+
const family = /* @__PURE__ */ new Map();
|
|
483
|
+
for (const cluster of chromatic) {
|
|
484
|
+
let share2 = 0;
|
|
485
|
+
for (const other of chromatic) {
|
|
486
|
+
if (hueDistance(cluster.h, other.h) <= 20) share2 += other.pop;
|
|
487
|
+
}
|
|
488
|
+
family.set(cluster, share2);
|
|
489
|
+
}
|
|
490
|
+
const share = (cluster) => family.get(cluster) ?? cluster.pop;
|
|
491
|
+
const withShare = (cluster) => cluster ? { ...cluster, pop: share(cluster) } : null;
|
|
492
|
+
const dominantPick = neutral ? null : pickBest(chromatic, (cluster) => share(cluster) * cluster.c * cluster.rely);
|
|
493
|
+
const vividPick = neutral ? null : pickBest(
|
|
494
|
+
chromatic.filter((cluster) => share(cluster) >= MIN_POPULATION),
|
|
495
|
+
(cluster) => cluster.c * cluster.c * Math.pow(share(cluster), 0.3) * cluster.rely
|
|
496
|
+
);
|
|
497
|
+
const accentPick = pickBest(
|
|
498
|
+
chromatic.filter(
|
|
499
|
+
(cluster) => neutral ? share(cluster) >= 6e-3 && cluster.c >= 0.06 : share(cluster) >= MIN_POPULATION && cluster.c >= 0.035 && (!dominantPick || hueDistance(cluster.h, dominantPick.h) >= minHueDistance)
|
|
500
|
+
),
|
|
501
|
+
(cluster) => Math.pow(share(cluster), 0.6) * cluster.c * cluster.rely
|
|
502
|
+
);
|
|
503
|
+
const mutedPick = neutral ? null : pickBest(
|
|
504
|
+
chromatic.filter((cluster) => cluster.c <= 0.09),
|
|
505
|
+
(cluster) => share(cluster) * cluster.rely * (1 - cluster.c / 0.12)
|
|
506
|
+
);
|
|
507
|
+
const dominantHue = dominantPick?.h ?? vividPick?.h ?? 0;
|
|
508
|
+
const roles = {
|
|
509
|
+
dominant: buildSwatch(
|
|
510
|
+
withShare(dominantPick),
|
|
511
|
+
{
|
|
512
|
+
cap: 0.22,
|
|
513
|
+
max: 0.74,
|
|
514
|
+
min: 0.42,
|
|
515
|
+
rel: dominantPick ? lift(dominantPick.rel, 0.25, 0.85, 0.9) : 0
|
|
516
|
+
},
|
|
517
|
+
grey,
|
|
518
|
+
true,
|
|
519
|
+
minContrast
|
|
520
|
+
),
|
|
521
|
+
vibrant: buildSwatch(
|
|
522
|
+
withShare(vividPick),
|
|
523
|
+
{
|
|
524
|
+
cap: 0.24,
|
|
525
|
+
max: 0.84,
|
|
526
|
+
min: 0.62,
|
|
527
|
+
rel: vividPick ? lift(vividPick.rel, 0.4, 0.75, 0.95) * dilution : 0
|
|
528
|
+
},
|
|
529
|
+
{ h: dominantHue, l: 0.78 },
|
|
530
|
+
true,
|
|
531
|
+
minContrast
|
|
532
|
+
),
|
|
533
|
+
muted: buildSwatch(
|
|
534
|
+
withShare(mutedPick),
|
|
535
|
+
{
|
|
536
|
+
cap: 0.08,
|
|
537
|
+
max: 0.66,
|
|
538
|
+
min: 0.38,
|
|
539
|
+
rel: mutedPick ? clamp(mutedPick.rel, 0.1, 0.3) : dominantPick ? 0.2 : 0
|
|
540
|
+
},
|
|
541
|
+
{ h: dominantHue, l: clamp(meanL, 0.38, 0.66) },
|
|
542
|
+
Boolean(mutedPick) || neutral,
|
|
543
|
+
minContrast
|
|
544
|
+
),
|
|
545
|
+
accent: buildSwatch(
|
|
546
|
+
withShare(accentPick ?? vividPick),
|
|
547
|
+
{
|
|
548
|
+
cap: 0.24,
|
|
549
|
+
max: 0.8,
|
|
550
|
+
min: 0.55,
|
|
551
|
+
rel: accentPick ?? vividPick ? lift((accentPick ?? vividPick).rel, 0.35, 0.75, 0.92) * dilution : 0
|
|
552
|
+
},
|
|
553
|
+
{ h: dominantHue, l: 0.72 },
|
|
554
|
+
Boolean(accentPick),
|
|
555
|
+
minContrast
|
|
556
|
+
)
|
|
557
|
+
};
|
|
558
|
+
const shadowPick = neutral ? null : pickBest(
|
|
559
|
+
clusters.filter(
|
|
560
|
+
(cluster) => cluster.l <= 0.5 && cluster.c >= 0.02 && cluster.pop >= 0.02
|
|
561
|
+
),
|
|
562
|
+
(cluster) => cluster.pop * cluster.rel * smoothstep(0.05, 0.14, cluster.l)
|
|
563
|
+
);
|
|
564
|
+
const deepSource = shadowPick ?? dominantPick;
|
|
565
|
+
const shadowL = clamp(
|
|
566
|
+
options.shadowLightness + (meanL - 0.45) * 0.06,
|
|
567
|
+
options.shadowLightness - 0.02,
|
|
568
|
+
options.shadowLightness + 0.02
|
|
569
|
+
);
|
|
570
|
+
const shadow = deepSource ? toneOf(deepSource.h, shadowL, lift(deepSource.rel, 0.3, 0.7, 0.85), 0.1) : { c: 0, h: 0, l: shadowL };
|
|
571
|
+
const regions = {};
|
|
572
|
+
AMBIENT_REGIONS.forEach((region, r) => {
|
|
573
|
+
const weights = regionWeights[r];
|
|
574
|
+
let regionTotal = 0;
|
|
575
|
+
let regionL = 0;
|
|
576
|
+
clusters.forEach((cluster, j) => {
|
|
577
|
+
regionTotal += weights[j];
|
|
578
|
+
regionL += cluster.l * weights[j];
|
|
579
|
+
});
|
|
580
|
+
const share2 = (j) => regionTotal > 0 ? weights[j] / regionTotal : 0;
|
|
581
|
+
let strength = 0;
|
|
582
|
+
let pick = null;
|
|
583
|
+
let pickScore = 0;
|
|
584
|
+
clusters.forEach((cluster, j) => {
|
|
585
|
+
if (!isChromatic(cluster)) return;
|
|
586
|
+
strength += share2(j) * colorfulness(cluster.c) * cluster.rely;
|
|
587
|
+
const score = share2(j) * cluster.c * cluster.rely;
|
|
588
|
+
if (score > pickScore) {
|
|
589
|
+
pickScore = score;
|
|
590
|
+
pick = cluster;
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
const chosen = neutral || strength < MIN_PRESENCE ? null : pick;
|
|
594
|
+
const tone = chosen ? toneOf(
|
|
595
|
+
chosen.h,
|
|
596
|
+
clamp(chosen.l, 0.42, 0.72),
|
|
597
|
+
lift(chosen.rel, 0.25, 0.85, 0.9),
|
|
598
|
+
0.22
|
|
599
|
+
) : {
|
|
600
|
+
c: 0,
|
|
601
|
+
h: 0,
|
|
602
|
+
l: clamp(regionTotal > 0 ? regionL / regionTotal : meanL, 0.3, 0.7)
|
|
603
|
+
};
|
|
604
|
+
regions[region] = {
|
|
605
|
+
c: round3(tone.c),
|
|
606
|
+
color: formatOklch(tone),
|
|
607
|
+
h: Math.round(tone.h * 10) / 10,
|
|
608
|
+
l: round3(tone.l),
|
|
609
|
+
strength: round3(chosen ? clamp(strength, 0, 1) : 0)
|
|
610
|
+
};
|
|
611
|
+
});
|
|
612
|
+
return {
|
|
613
|
+
neutral,
|
|
614
|
+
regions,
|
|
615
|
+
roles,
|
|
616
|
+
shadow: formatOklch(shadow)
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function fitSample(width, height, size) {
|
|
620
|
+
const scale = Math.min(1, size / Math.max(width, height, 1));
|
|
621
|
+
return {
|
|
622
|
+
height: Math.max(1, Math.round(height * scale)),
|
|
623
|
+
width: Math.max(1, Math.round(width * scale))
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// src/modules/ambient/pipeline.ts
|
|
628
|
+
async function fetchImageBlob(src, proxyUrl) {
|
|
629
|
+
if (typeof fetch !== "function") return null;
|
|
630
|
+
const read = async (url, init) => {
|
|
631
|
+
const response = await fetch(url, { ...init, mode: "cors" });
|
|
632
|
+
if (!response.ok) return null;
|
|
633
|
+
const blob = await response.blob();
|
|
634
|
+
return blob.type.startsWith("text/") ? null : blob;
|
|
635
|
+
};
|
|
636
|
+
try {
|
|
637
|
+
const blob = await read(src);
|
|
638
|
+
if (blob) return blob;
|
|
639
|
+
} catch {
|
|
640
|
+
if (isCrossOrigin(src)) {
|
|
641
|
+
try {
|
|
642
|
+
const blob = await read(src, { cache: "reload" });
|
|
643
|
+
if (blob) return blob;
|
|
644
|
+
} catch {
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (proxyUrl) {
|
|
649
|
+
try {
|
|
650
|
+
return await read(proxyUrl);
|
|
651
|
+
} catch {
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
function isCrossOrigin(url) {
|
|
657
|
+
try {
|
|
658
|
+
return new URL(url).origin !== globalThis.location?.origin;
|
|
659
|
+
} catch {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
function createContext2D(width, height) {
|
|
664
|
+
if (typeof OffscreenCanvas === "function") {
|
|
665
|
+
return new OffscreenCanvas(width, height).getContext("2d", {
|
|
666
|
+
willReadFrequently: true
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
if (typeof document !== "undefined") {
|
|
670
|
+
const canvas = document.createElement("canvas");
|
|
671
|
+
canvas.width = width;
|
|
672
|
+
canvas.height = height;
|
|
673
|
+
return canvas.getContext("2d", { willReadFrequently: true });
|
|
674
|
+
}
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
function readPixels(image, sourceWidth, sourceHeight, size) {
|
|
678
|
+
const { height, width } = fitSample(sourceWidth, sourceHeight, size);
|
|
679
|
+
const context = createContext2D(width, height);
|
|
680
|
+
if (!context) return null;
|
|
681
|
+
context.drawImage(image, 0, 0, width, height);
|
|
682
|
+
return { data: context.getImageData(0, 0, width, height).data, height, width };
|
|
683
|
+
}
|
|
684
|
+
async function decodeBlob(blob, size) {
|
|
685
|
+
if (typeof createImageBitmap !== "function") return null;
|
|
686
|
+
let bitmap;
|
|
687
|
+
try {
|
|
688
|
+
bitmap = await createImageBitmap(blob, {
|
|
689
|
+
resizeQuality: "medium",
|
|
690
|
+
resizeWidth: size
|
|
691
|
+
});
|
|
692
|
+
} catch {
|
|
693
|
+
bitmap = await createImageBitmap(blob);
|
|
694
|
+
}
|
|
695
|
+
try {
|
|
696
|
+
return readPixels(bitmap, bitmap.width, bitmap.height, size);
|
|
697
|
+
} finally {
|
|
698
|
+
bitmap.close();
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
var canDecodeHere = () => typeof createImageBitmap === "function" && (typeof OffscreenCanvas === "function" || typeof document !== "undefined");
|
|
702
|
+
async function extractHere(request) {
|
|
703
|
+
if (!canDecodeHere()) return { supported: false };
|
|
704
|
+
const blob = await fetchImageBlob(request.src, request.proxyUrl);
|
|
705
|
+
if (!blob) return { palette: null, supported: true };
|
|
706
|
+
try {
|
|
707
|
+
const pixels = await decodeBlob(blob, request.analysis.sampleSize);
|
|
708
|
+
return {
|
|
709
|
+
palette: pixels ? analyzeAmbientPixels(pixels, request.analysis) : null,
|
|
710
|
+
supported: true
|
|
711
|
+
};
|
|
712
|
+
} catch {
|
|
713
|
+
return { palette: null, supported: true };
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/modules/ambient/worker/index.ts
|
|
718
|
+
var AMBIENT_WORKER_REQUEST = "ambient:extract";
|
|
719
|
+
var AMBIENT_WORKER_RESPONSE = "ambient:palette";
|
|
720
|
+
|
|
721
|
+
// src/modules/ambient/extractor.ts
|
|
722
|
+
var WORKER_PATIENCE = 15e3;
|
|
723
|
+
var paletteCache = /* @__PURE__ */ new Map();
|
|
724
|
+
var inFlight = /* @__PURE__ */ new Map();
|
|
725
|
+
function cachePalette(key, palette) {
|
|
726
|
+
paletteCache.delete(key);
|
|
727
|
+
const limit = Math.max(1, getAmbientSettings().cacheLimit);
|
|
728
|
+
while (paletteCache.size >= limit) {
|
|
729
|
+
const oldest = paletteCache.keys().next().value;
|
|
730
|
+
if (oldest === void 0) break;
|
|
731
|
+
paletteCache.delete(oldest);
|
|
732
|
+
}
|
|
733
|
+
paletteCache.set(key, palette);
|
|
734
|
+
}
|
|
735
|
+
function primeAmbientPalette(src, palette) {
|
|
736
|
+
if (src && palette) cachePalette(src, { ...palette, source: src });
|
|
737
|
+
}
|
|
738
|
+
function getCachedAmbientPalette(src) {
|
|
739
|
+
return paletteCache.get(src) ?? null;
|
|
740
|
+
}
|
|
741
|
+
var slot = null;
|
|
742
|
+
var brokenFactories = /* @__PURE__ */ new WeakSet();
|
|
743
|
+
function dropWorker(target) {
|
|
744
|
+
if (slot === target) slot = null;
|
|
745
|
+
target.worker.terminate();
|
|
746
|
+
for (const resolve of target.pending.values()) resolve(null);
|
|
747
|
+
target.pending.clear();
|
|
748
|
+
}
|
|
749
|
+
function workerSlot(factory) {
|
|
750
|
+
if (slot?.factory === factory) return slot;
|
|
751
|
+
if (slot) dropWorker(slot);
|
|
752
|
+
if (brokenFactories.has(factory)) return null;
|
|
753
|
+
let worker;
|
|
754
|
+
try {
|
|
755
|
+
worker = factory();
|
|
756
|
+
} catch {
|
|
757
|
+
brokenFactories.add(factory);
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
const created = { factory, next: 1, pending: /* @__PURE__ */ new Map(), worker };
|
|
761
|
+
worker.addEventListener("message", (event) => {
|
|
762
|
+
const message = event.data;
|
|
763
|
+
if (message?.type !== AMBIENT_WORKER_RESPONSE) return;
|
|
764
|
+
created.pending.get(message.id)?.(message);
|
|
765
|
+
created.pending.delete(message.id);
|
|
766
|
+
});
|
|
767
|
+
worker.addEventListener("error", () => {
|
|
768
|
+
brokenFactories.add(factory);
|
|
769
|
+
dropWorker(created);
|
|
770
|
+
});
|
|
771
|
+
slot = created;
|
|
772
|
+
return created;
|
|
773
|
+
}
|
|
774
|
+
function extractInWorker(factory, request) {
|
|
775
|
+
const target = workerSlot(factory);
|
|
776
|
+
if (!target) return Promise.resolve(null);
|
|
777
|
+
const id = target.next++;
|
|
778
|
+
return new Promise((resolve) => {
|
|
779
|
+
const timer = setTimeout(() => {
|
|
780
|
+
target.pending.delete(id);
|
|
781
|
+
resolve(null);
|
|
782
|
+
}, WORKER_PATIENCE);
|
|
783
|
+
target.pending.set(id, (result) => {
|
|
784
|
+
clearTimeout(timer);
|
|
785
|
+
resolve(result);
|
|
786
|
+
});
|
|
787
|
+
target.worker.postMessage({ ...request, id, type: AMBIENT_WORKER_REQUEST });
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
function loadImageElement(url) {
|
|
791
|
+
return new Promise((resolve) => {
|
|
792
|
+
const image = new Image();
|
|
793
|
+
image.onload = () => resolve(image);
|
|
794
|
+
image.onerror = () => resolve(null);
|
|
795
|
+
image.src = url;
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
async function extractOnMainThread(request) {
|
|
799
|
+
const result = await extractHere(request);
|
|
800
|
+
if (result.supported) return result.palette;
|
|
801
|
+
const blob = await fetchImageBlob(request.src, request.proxyUrl);
|
|
802
|
+
if (!blob || typeof URL.createObjectURL !== "function") return null;
|
|
803
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
804
|
+
try {
|
|
805
|
+
const image = await loadImageElement(objectUrl);
|
|
806
|
+
const pixels = image && readPixels(
|
|
807
|
+
image,
|
|
808
|
+
image.naturalWidth,
|
|
809
|
+
image.naturalHeight,
|
|
810
|
+
request.analysis.sampleSize
|
|
811
|
+
);
|
|
812
|
+
return pixels ? analyzeAmbientPixels(pixels, request.analysis) : null;
|
|
813
|
+
} catch {
|
|
814
|
+
return null;
|
|
815
|
+
} finally {
|
|
816
|
+
URL.revokeObjectURL(objectUrl);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
function absoluteUrl(src) {
|
|
820
|
+
try {
|
|
821
|
+
return new URL(src, globalThis.location?.href).href;
|
|
822
|
+
} catch {
|
|
823
|
+
return src;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
var isCrossOrigin2 = (url) => {
|
|
827
|
+
try {
|
|
828
|
+
return new URL(url).origin !== globalThis.location?.origin;
|
|
829
|
+
} catch {
|
|
830
|
+
return false;
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
async function extractPalette(src, options) {
|
|
834
|
+
const settings = getAmbientSettings();
|
|
835
|
+
const strategy = options.strategy ?? settings.strategy;
|
|
836
|
+
const absolute = absoluteUrl(src);
|
|
837
|
+
const proxy = options.proxy === void 0 ? settings.proxy : options.proxy;
|
|
838
|
+
const request = {
|
|
839
|
+
analysis: resolveAnalysis(options.analysis),
|
|
840
|
+
proxyUrl: isCrossOrigin2(absolute) ? (() => {
|
|
841
|
+
const proxied = resolveProxyUrl(proxy, absolute);
|
|
842
|
+
return proxied ? absoluteUrl(proxied) : null;
|
|
843
|
+
})() : null,
|
|
844
|
+
src: absolute
|
|
845
|
+
};
|
|
846
|
+
if (strategy !== "main" && settings.worker && typeof Worker === "function") {
|
|
847
|
+
const result = await extractInWorker(settings.worker, request);
|
|
848
|
+
if (result?.supported) return result.palette;
|
|
849
|
+
}
|
|
850
|
+
if (strategy === "worker") return null;
|
|
851
|
+
return extractOnMainThread(request);
|
|
852
|
+
}
|
|
853
|
+
async function extractPaletteFromImage(imageSource, options = {}) {
|
|
854
|
+
const src = typeof imageSource === "string" ? imageSource : imageSource?.src;
|
|
855
|
+
if (!src || typeof window === "undefined") return null;
|
|
856
|
+
const strategy = options.strategy ?? getAmbientSettings().strategy;
|
|
857
|
+
if (strategy === "none") return getCachedAmbientPalette(src);
|
|
858
|
+
const key = options.analysis ? `${src}#${JSON.stringify(options.analysis)}` : src;
|
|
859
|
+
const cached = paletteCache.get(key);
|
|
860
|
+
if (cached) return cached;
|
|
861
|
+
let pending = inFlight.get(key);
|
|
862
|
+
if (!pending) {
|
|
863
|
+
pending = extractPalette(src, options).catch(() => null).then((palette) => {
|
|
864
|
+
if (!palette) return null;
|
|
865
|
+
const sourced = { ...palette, source: src };
|
|
866
|
+
cachePalette(key, sourced);
|
|
867
|
+
return sourced;
|
|
868
|
+
}).finally(() => inFlight.delete(key));
|
|
869
|
+
inFlight.set(key, pending);
|
|
870
|
+
}
|
|
871
|
+
return pending;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/modules/ambient/dom.ts
|
|
875
|
+
function resolveCssVarNames(overrides) {
|
|
876
|
+
const base = getAmbientSettings().cssVars;
|
|
877
|
+
return overrides ? { ...base, ...overrides } : base;
|
|
878
|
+
}
|
|
879
|
+
var fill = (template, values) => template.replace(
|
|
880
|
+
/\{(\w+)\}/g,
|
|
881
|
+
(match, key) => key in values ? String(values[key]) : match
|
|
882
|
+
);
|
|
883
|
+
function resolveTargetElement(target) {
|
|
884
|
+
if (typeof document === "undefined") return null;
|
|
885
|
+
if (!target) return document.documentElement;
|
|
886
|
+
if (typeof target === "string") {
|
|
887
|
+
return document.querySelector(target);
|
|
888
|
+
}
|
|
889
|
+
return "current" in target ? target.current : target;
|
|
890
|
+
}
|
|
891
|
+
function normalizeColorMap(colors) {
|
|
892
|
+
const result = {};
|
|
893
|
+
for (const [key, value] of Object.entries(colors)) {
|
|
894
|
+
if (!value) continue;
|
|
895
|
+
result[key.startsWith("--") ? key : `--color-${key}`] = value;
|
|
896
|
+
}
|
|
897
|
+
return result;
|
|
898
|
+
}
|
|
899
|
+
function applyScopedCssVariables(element, varMap) {
|
|
900
|
+
const previous = /* @__PURE__ */ new Map();
|
|
901
|
+
for (const [name, value] of Object.entries(varMap)) {
|
|
902
|
+
if (!name || !value) continue;
|
|
903
|
+
previous.set(name, {
|
|
904
|
+
priority: element.style.getPropertyPriority(name),
|
|
905
|
+
value: element.style.getPropertyValue(name)
|
|
906
|
+
});
|
|
907
|
+
element.style.setProperty(name, value);
|
|
908
|
+
}
|
|
909
|
+
return () => {
|
|
910
|
+
for (const [name, { priority, value }] of previous) {
|
|
911
|
+
if (value) element.style.setProperty(name, value, priority);
|
|
912
|
+
else element.style.removeProperty(name);
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
function paletteVarMap(palette, names = resolveCssVarNames()) {
|
|
917
|
+
const varMap = {};
|
|
918
|
+
if (!palette) return varMap;
|
|
919
|
+
if (names.shadow) varMap[names.shadow] = palette.shadow;
|
|
920
|
+
for (const role of AMBIENT_ROLES) {
|
|
921
|
+
const swatch = palette.roles[role];
|
|
922
|
+
if (names.role) varMap[fill(names.role, { role })] = swatch.color;
|
|
923
|
+
if (names.on) varMap[fill(names.on, { role })] = swatch.on;
|
|
924
|
+
if (names.tone && names.tones) {
|
|
925
|
+
for (const tone of names.tones) {
|
|
926
|
+
varMap[fill(names.tone, { role, tone })] = swatch.ramp[tone];
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (names.region) {
|
|
931
|
+
for (const region of AMBIENT_REGIONS) {
|
|
932
|
+
varMap[fill(names.region, { region })] = palette.regions[region].color;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return varMap;
|
|
936
|
+
}
|
|
937
|
+
function resolveAmbientVarMap({
|
|
938
|
+
colors = null,
|
|
939
|
+
cssVars = null,
|
|
940
|
+
image = null,
|
|
941
|
+
palette = null
|
|
942
|
+
}) {
|
|
943
|
+
const varMap = image ? paletteVarMap(palette, resolveCssVarNames(cssVars)) : {};
|
|
944
|
+
if (colors) Object.assign(varMap, normalizeColorMap(colors));
|
|
945
|
+
return varMap;
|
|
946
|
+
}
|
|
947
|
+
function useStableObject(value) {
|
|
948
|
+
const ref = useRef(value);
|
|
949
|
+
if (!shallowEqual(ref.current, value)) {
|
|
950
|
+
ref.current = value;
|
|
951
|
+
}
|
|
952
|
+
return ref.current;
|
|
953
|
+
}
|
|
954
|
+
function useAmbientColor(imageSource, options = null) {
|
|
955
|
+
const stableOptions = useStableObject(options);
|
|
956
|
+
const initialPalette = useStableObject(options?.initialPalette ?? null);
|
|
957
|
+
const srcKey = typeof imageSource === "string" ? imageSource : imageSource?.src;
|
|
958
|
+
const seeded = srcKey && initialPalette?.source === srcKey ? initialPalette : null;
|
|
959
|
+
const [result, setResult] = useState(null);
|
|
960
|
+
const settled = result?.source === srcKey ? result : null;
|
|
961
|
+
const isExtracting = Boolean(imageSource && !seeded && !settled);
|
|
962
|
+
useEffect(() => {
|
|
963
|
+
if (seeded && srcKey) primeAmbientPalette(srcKey, seeded);
|
|
964
|
+
}, [seeded, srcKey]);
|
|
965
|
+
useEffect(() => {
|
|
966
|
+
if (!imageSource || !srcKey || seeded) return;
|
|
967
|
+
let active = true;
|
|
968
|
+
void extractPaletteFromImage(imageSource, stableOptions ?? {}).catch(() => null).then((palette2) => {
|
|
969
|
+
if (active) setResult({ palette: palette2, source: srcKey });
|
|
970
|
+
});
|
|
971
|
+
return () => {
|
|
972
|
+
active = false;
|
|
973
|
+
};
|
|
974
|
+
}, [imageSource, seeded, srcKey, stableOptions]);
|
|
975
|
+
const palette = imageSource ? seeded ?? settled?.palette ?? initialPalette : initialPalette;
|
|
976
|
+
return { isExtracting, palette };
|
|
977
|
+
}
|
|
978
|
+
function useAmbientTheme(config = null) {
|
|
979
|
+
const ambientActions = use(AmbientContext)?.actions ?? null;
|
|
980
|
+
const normalizedConfig = typeof config === "string" ? { image: config } : config || {};
|
|
981
|
+
const {
|
|
982
|
+
colors = null,
|
|
983
|
+
cssVars = null,
|
|
984
|
+
image = null,
|
|
985
|
+
initialPalette = null,
|
|
986
|
+
options = null,
|
|
987
|
+
scope: scope2 = null,
|
|
988
|
+
transition = true
|
|
989
|
+
} = normalizedConfig;
|
|
990
|
+
const stableInitialPalette = useStableObject(initialPalette);
|
|
991
|
+
const stableOptions = useStableObject(options);
|
|
992
|
+
const stableColors = useStableObject(colors);
|
|
993
|
+
const stableCssVars = useStableObject(cssVars);
|
|
994
|
+
const resolvedOptions = useMemo(
|
|
995
|
+
() => ({
|
|
996
|
+
...stableOptions || {},
|
|
997
|
+
...stableInitialPalette ? { initialPalette: stableInitialPalette } : {}
|
|
998
|
+
}),
|
|
999
|
+
[stableInitialPalette, stableOptions]
|
|
1000
|
+
);
|
|
1001
|
+
const { palette: extractedPalette, isExtracting } = useAmbientColor(
|
|
1002
|
+
image,
|
|
1003
|
+
resolvedOptions
|
|
1004
|
+
);
|
|
1005
|
+
const hasActiveConfig = Boolean(image || stableColors);
|
|
1006
|
+
useEffect(() => {
|
|
1007
|
+
if (!hasActiveConfig || !ambientActions) return;
|
|
1008
|
+
ambientActions.setIsExtracting(isExtracting);
|
|
1009
|
+
ambientActions.setPalette(
|
|
1010
|
+
(prev) => shallowEqual(prev, extractedPalette) ? prev : extractedPalette
|
|
1011
|
+
);
|
|
1012
|
+
}, [ambientActions, extractedPalette, hasActiveConfig, isExtracting]);
|
|
1013
|
+
const transitionClasses = useTheme(ambientTheme).slots.transition.split(/\s+/).filter(Boolean);
|
|
1014
|
+
const transitionKey = transitionClasses.join(" ");
|
|
1015
|
+
const resolvedVarMap = useMemo(
|
|
1016
|
+
() => resolveAmbientVarMap({
|
|
1017
|
+
colors: stableColors,
|
|
1018
|
+
cssVars: stableCssVars,
|
|
1019
|
+
image,
|
|
1020
|
+
palette: extractedPalette
|
|
1021
|
+
}),
|
|
1022
|
+
[extractedPalette, image, stableColors, stableCssVars]
|
|
1023
|
+
);
|
|
1024
|
+
useEffect(() => {
|
|
1025
|
+
if (Object.keys(resolvedVarMap).length === 0) return;
|
|
1026
|
+
const targetEl = resolveTargetElement(scope2);
|
|
1027
|
+
if (!targetEl) return;
|
|
1028
|
+
if (transition) {
|
|
1029
|
+
targetEl.classList.add(...transitionKey.split(" ").filter(Boolean));
|
|
1030
|
+
}
|
|
1031
|
+
const restoreVariables = applyScopedCssVariables(targetEl, resolvedVarMap);
|
|
1032
|
+
return () => {
|
|
1033
|
+
restoreVariables();
|
|
1034
|
+
if (transition && scope2) {
|
|
1035
|
+
targetEl.classList.remove(...transitionKey.split(" ").filter(Boolean));
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
}, [resolvedVarMap, scope2, transition, transitionKey]);
|
|
1039
|
+
return { isExtracting, palette: extractedPalette };
|
|
1040
|
+
}
|
|
1041
|
+
function useAmbient(configOrImage) {
|
|
1042
|
+
const { actions, store } = useRequiredContext(
|
|
1043
|
+
AmbientContext,
|
|
1044
|
+
"useAmbient",
|
|
1045
|
+
"AmbientProvider"
|
|
1046
|
+
);
|
|
1047
|
+
const shared = useStore(store);
|
|
1048
|
+
const ctx = useMemo(
|
|
1049
|
+
() => ({ ...shared, ...actions }),
|
|
1050
|
+
[actions, shared]
|
|
1051
|
+
);
|
|
1052
|
+
const { isExtracting, palette } = useAmbientTheme(configOrImage ?? null);
|
|
1053
|
+
const themeState = useMemo(
|
|
1054
|
+
() => ({ isExtracting, palette }),
|
|
1055
|
+
[isExtracting, palette]
|
|
1056
|
+
);
|
|
1057
|
+
return useMemo(
|
|
1058
|
+
() => configOrImage ? { ...ctx, ...themeState } : ctx,
|
|
1059
|
+
[configOrImage, ctx, themeState]
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
var firstString = (...values) => values.find((value) => typeof value === "string" && value) ?? null;
|
|
1063
|
+
function resolvePageAmbientTheme(slice, posterUrl) {
|
|
1064
|
+
const ambient = slice?.ambient;
|
|
1065
|
+
const banner = slice?.banner ?? null;
|
|
1066
|
+
const defaultImage = banner ?? posterUrl;
|
|
1067
|
+
if (ambient === false) return null;
|
|
1068
|
+
if (ambient === true || typeof ambient === "string") {
|
|
1069
|
+
return { image: typeof ambient === "string" ? ambient : defaultImage };
|
|
1070
|
+
}
|
|
1071
|
+
if (ambient && typeof ambient === "object") {
|
|
1072
|
+
return { image: ambient.image ?? defaultImage, ...ambient };
|
|
1073
|
+
}
|
|
1074
|
+
if (banner && !posterUrl) return { image: banner };
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
function selectPageAmbient(config) {
|
|
1078
|
+
return {
|
|
1079
|
+
ambient: config.ambient,
|
|
1080
|
+
banner: firstString(config.dock?.banner, config.dock?.bannerUrl)
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
function useAmbientPage(slice, page) {
|
|
1084
|
+
const posterUrl = useOptionalBackgroundState(
|
|
1085
|
+
(background) => background.posterUrl
|
|
1086
|
+
);
|
|
1087
|
+
const theme = useMemo(
|
|
1088
|
+
() => resolvePageAmbientTheme(slice, posterUrl),
|
|
1089
|
+
[posterUrl, slice]
|
|
1090
|
+
);
|
|
1091
|
+
useAmbientTheme(theme);
|
|
1092
|
+
return useMemo(
|
|
1093
|
+
() => ({ set: (ambient) => page.set({ ambient }) }),
|
|
1094
|
+
[page]
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
var ambientModule = defineModule({
|
|
1098
|
+
id: "ambient",
|
|
1099
|
+
context: AmbientContext,
|
|
1100
|
+
Provider: AmbientProvider,
|
|
1101
|
+
uses: ["background"],
|
|
1102
|
+
page: {
|
|
1103
|
+
select: selectPageAmbient,
|
|
1104
|
+
use: useAmbientPage
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
function resolveValue(value, props) {
|
|
1108
|
+
return typeof value === "function" ? value(props) : value;
|
|
1109
|
+
}
|
|
1110
|
+
function defineAmbient(definition = {}) {
|
|
1111
|
+
const { defaultProps, id = "ambient-theme", ...config } = definition;
|
|
1112
|
+
const create = (props = {}, { id: overrideId, ...overrides } = {}) => {
|
|
1113
|
+
const mergedProps = { ...defaultProps, ...props };
|
|
1114
|
+
const merged = { ...config, ...overrides };
|
|
1115
|
+
return {
|
|
1116
|
+
...merged,
|
|
1117
|
+
colors: resolveValue(merged.colors ?? null, mergedProps) ?? null,
|
|
1118
|
+
id: overrideId ?? id,
|
|
1119
|
+
image: resolveValue(merged.image ?? null, mergedProps),
|
|
1120
|
+
initialPalette: merged.initialPalette ?? null,
|
|
1121
|
+
options: { ...config.options, ...overrides.options },
|
|
1122
|
+
props: mergedProps,
|
|
1123
|
+
scope: merged.scope ?? null,
|
|
1124
|
+
transition: merged.transition ?? true
|
|
1125
|
+
};
|
|
1126
|
+
};
|
|
1127
|
+
return Object.freeze({
|
|
1128
|
+
config: definition,
|
|
1129
|
+
create,
|
|
1130
|
+
id,
|
|
1131
|
+
use: function useDefinedAmbient(props, overrides) {
|
|
1132
|
+
return useAmbientTheme(create(props, overrides));
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
export { AMBIENT_REGIONS, AMBIENT_ROLES, AMBIENT_TONES, AmbientContext, AmbientProvider, DEFAULT_ANALYSIS, DEFAULT_CSS_VAR_NAMES, ambientModule, ambientTheme, analyzeAmbientPixels, applyScopedCssVariables, configureAmbient, contrastRatio, defineAmbient, extractPaletteFromImage, findContentBounds, formatOklch, getAmbientSettings, getCachedAmbientPalette, maxChroma, normalizeColorMap, onColorFor, paletteVarMap, primeAmbientPalette, resolveAmbientVarMap, resolveCssVarNames, resolvePageAmbientTheme, resolveTargetElement, srgbToOklab, toneOf, useAmbient, useAmbientColor, useAmbientTheme };
|
|
1138
|
+
//# sourceMappingURL=chunk-QXR55VVS.js.map
|
|
1139
|
+
//# sourceMappingURL=chunk-QXR55VVS.js.map
|