@omerdlw/base-framework 2.0.1 → 3.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.
Files changed (94) hide show
  1. package/README.md +4 -1
  2. package/dist/{chunk-XASQT4CQ.js → chunk-66EYJBIP.js} +47 -27
  3. package/dist/chunk-66EYJBIP.js.map +1 -0
  4. package/dist/{chunk-SKBITB5X.js → chunk-CYU6SGAL.js} +3 -3
  5. package/dist/{chunk-SKBITB5X.js.map → chunk-CYU6SGAL.js.map} +1 -1
  6. package/dist/{chunk-6SG7V77J.js → chunk-HHWRTQKQ.js} +100 -67
  7. package/dist/chunk-HHWRTQKQ.js.map +1 -0
  8. package/dist/chunk-HLL5EHXC.js +485 -0
  9. package/dist/chunk-HLL5EHXC.js.map +1 -0
  10. package/dist/{chunk-A5RE3F6O.js → chunk-JIXBFFRD.js} +193 -357
  11. package/dist/chunk-JIXBFFRD.js.map +1 -0
  12. package/dist/{chunk-6ZJVLUBE.js → chunk-M57SNWXK.js} +8 -6
  13. package/dist/chunk-M57SNWXK.js.map +1 -0
  14. package/dist/chunk-RIOKLMYM.js +145 -0
  15. package/dist/chunk-RIOKLMYM.js.map +1 -0
  16. package/dist/{chunk-QXR55VVS.js → chunk-TG6NMHXI.js} +241 -162
  17. package/dist/chunk-TG6NMHXI.js.map +1 -0
  18. package/dist/index.js +7 -5
  19. package/dist/modules/ambient/analyze.d.ts +18 -3
  20. package/dist/modules/ambient/analyze.d.ts.map +1 -1
  21. package/dist/modules/ambient/dom.d.ts +14 -1
  22. package/dist/modules/ambient/dom.d.ts.map +1 -1
  23. package/dist/modules/ambient/hooks.d.ts.map +1 -1
  24. package/dist/modules/ambient/index.d.ts +2 -2
  25. package/dist/modules/ambient/index.d.ts.map +1 -1
  26. package/dist/modules/ambient/index.js +1 -1
  27. package/dist/modules/ambient/server/index.d.ts +1 -1
  28. package/dist/modules/ambient/server/index.d.ts.map +1 -1
  29. package/dist/modules/ambient/server/index.js +2 -2
  30. package/dist/modules/ambient/server/index.js.map +1 -1
  31. package/dist/modules/ambient/settings.d.ts +1 -0
  32. package/dist/modules/ambient/settings.d.ts.map +1 -1
  33. package/dist/modules/ambient/types.d.ts +13 -0
  34. package/dist/modules/ambient/types.d.ts.map +1 -1
  35. package/dist/modules/ambient/worker/index.js +1 -1
  36. package/dist/modules/context-menu/index.js +4 -3
  37. package/dist/modules/dock/cards.d.ts.map +1 -1
  38. package/dist/modules/dock/constants.d.ts +1 -20
  39. package/dist/modules/dock/constants.d.ts.map +1 -1
  40. package/dist/modules/dock/index.js +3 -2
  41. package/dist/modules/dock/routing/guards.d.ts +1 -1
  42. package/dist/modules/dock/routing/navigation.d.ts +7 -4
  43. package/dist/modules/dock/routing/navigation.d.ts.map +1 -1
  44. package/dist/modules/dock/runtime/display.d.ts +31 -2
  45. package/dist/modules/dock/runtime/display.d.ts.map +1 -1
  46. package/dist/modules/dock/runtime/dock.d.ts +2 -2
  47. package/dist/modules/dock/runtime/dock.d.ts.map +1 -1
  48. package/dist/modules/dock/types.d.ts +5 -27
  49. package/dist/modules/dock/types.d.ts.map +1 -1
  50. package/dist/modules/index.d.ts +3 -2
  51. package/dist/modules/index.d.ts.map +1 -1
  52. package/dist/modules/index.js +7 -5
  53. package/dist/modules/loading/context.d.ts.map +1 -1
  54. package/dist/modules/loading/index.js +1 -1
  55. package/dist/modules/navigation/constants.d.ts +37 -0
  56. package/dist/modules/navigation/constants.d.ts.map +1 -0
  57. package/dist/modules/navigation/context.d.ts +10 -0
  58. package/dist/modules/navigation/context.d.ts.map +1 -0
  59. package/dist/modules/navigation/hooks.d.ts +29 -0
  60. package/dist/modules/navigation/hooks.d.ts.map +1 -0
  61. package/dist/modules/navigation/index.d.ts +10 -0
  62. package/dist/modules/navigation/index.d.ts.map +1 -0
  63. package/dist/modules/navigation/index.js +81 -0
  64. package/dist/modules/navigation/index.js.map +1 -0
  65. package/dist/modules/navigation/listeners.d.ts +15 -0
  66. package/dist/modules/navigation/listeners.d.ts.map +1 -0
  67. package/dist/modules/navigation/module.d.ts +25 -0
  68. package/dist/modules/navigation/module.d.ts.map +1 -0
  69. package/dist/modules/navigation/navigator.d.ts +8 -0
  70. package/dist/modules/navigation/navigator.d.ts.map +1 -0
  71. package/dist/modules/navigation/settings.d.ts +10 -0
  72. package/dist/modules/navigation/settings.d.ts.map +1 -0
  73. package/dist/modules/navigation/state.d.ts +20 -0
  74. package/dist/modules/navigation/state.d.ts.map +1 -0
  75. package/dist/modules/navigation/types.d.ts +168 -0
  76. package/dist/modules/navigation/types.d.ts.map +1 -0
  77. package/dist/modules/navigation/utils.d.ts +11 -0
  78. package/dist/modules/navigation/utils.d.ts.map +1 -0
  79. package/docs/getting-started.md +2 -0
  80. package/docs/modules/README.md +5 -1
  81. package/docs/modules/ambient.md +9 -6
  82. package/docs/modules/dock.md +12 -10
  83. package/docs/modules/loading.md +2 -2
  84. package/docs/modules/navigation.md +216 -0
  85. package/docs/proposals/navigation.md +209 -0
  86. package/package.json +6 -1
  87. package/templates/AGENTS.md +2 -0
  88. package/dist/chunk-6SG7V77J.js.map +0 -1
  89. package/dist/chunk-6ZJVLUBE.js.map +0 -1
  90. package/dist/chunk-A5RE3F6O.js.map +0 -1
  91. package/dist/chunk-QXR55VVS.js.map +0 -1
  92. package/dist/chunk-XASQT4CQ.js.map +0 -1
  93. package/dist/modules/dock/routing/transactions.d.ts +0 -21
  94. package/dist/modules/dock/routing/transactions.d.ts.map +0 -1
@@ -38,6 +38,94 @@ function AmbientProvider({
38
38
  // src/modules/ambient/constants.ts
39
39
  var ambientTheme = defineThemeSpec("ambient");
40
40
 
41
+ // src/modules/ambient/settings.ts
42
+ var AMBIENT_TONES = Object.freeze([
43
+ 10,
44
+ 15,
45
+ 20,
46
+ 30,
47
+ 40,
48
+ 50,
49
+ 60,
50
+ 70,
51
+ 80,
52
+ 90,
53
+ 95
54
+ ]);
55
+ var AMBIENT_ROLES = Object.freeze([
56
+ "dominant",
57
+ "vibrant",
58
+ "muted",
59
+ "accent"
60
+ ]);
61
+ var AMBIENT_REGIONS = Object.freeze([
62
+ "top",
63
+ "bottom",
64
+ "left",
65
+ "right",
66
+ "center"
67
+ ]);
68
+ var DEFAULT_ANALYSIS = Object.freeze({
69
+ centerWeight: 0.5,
70
+ clusters: 7,
71
+ letterbox: true,
72
+ minContrast: 4.5,
73
+ minHueDistance: 40,
74
+ sampleSize: 128,
75
+ shadowLightness: 0.16
76
+ });
77
+ var DEFAULT_CSS_VAR_NAMES = Object.freeze({
78
+ on: "--ambient-on-{role}",
79
+ region: "--ambient-{region}",
80
+ role: "--ambient-{role}",
81
+ shadow: "--ambient-shadow",
82
+ tone: "--ambient-{role}-{tone}"
83
+ });
84
+ var DEFAULT_PROXY = "/api/ambient/proxy?url=";
85
+ var DEFAULT_LINGER_MS = 3e3;
86
+ var createDefaults = () => ({
87
+ analysis: { ...DEFAULT_ANALYSIS },
88
+ cacheLimit: 100,
89
+ cssVars: { ...DEFAULT_CSS_VAR_NAMES, tones: AMBIENT_TONES },
90
+ linger: DEFAULT_LINGER_MS,
91
+ proxy: DEFAULT_PROXY,
92
+ strategy: "auto",
93
+ worker: null
94
+ });
95
+ var SETTINGS_KEY = /* @__PURE__ */ Symbol.for("@omerdlw/base-framework/ambient/settings");
96
+ var scope = globalThis;
97
+ function getAmbientSettings() {
98
+ return scope[SETTINGS_KEY] ??= createDefaults();
99
+ }
100
+ function mergeAmbientSettings(base, input) {
101
+ return {
102
+ analysis: { ...base.analysis, ...input.analysis },
103
+ cacheLimit: input.cacheLimit ?? base.cacheLimit,
104
+ cssVars: { ...base.cssVars, ...input.cssVars },
105
+ linger: input.linger ?? base.linger,
106
+ proxy: input.proxy === void 0 ? base.proxy : input.proxy,
107
+ strategy: input.strategy ?? base.strategy,
108
+ worker: input.worker === void 0 ? base.worker : input.worker
109
+ };
110
+ }
111
+ function configureAmbient(input) {
112
+ const previous = getAmbientSettings();
113
+ const next = mergeAmbientSettings(previous, input);
114
+ scope[SETTINGS_KEY] = next;
115
+ return () => {
116
+ if (scope[SETTINGS_KEY] === next) scope[SETTINGS_KEY] = previous;
117
+ };
118
+ }
119
+ function resolveAnalysis(overrides) {
120
+ return { ...getAmbientSettings().analysis, ...overrides };
121
+ }
122
+ function resolveProxyUrl(proxy, src) {
123
+ if (!proxy || !/^https?:\/\//i.test(src)) return null;
124
+ if (typeof proxy === "function") return proxy(src);
125
+ const encoded = encodeURIComponent(src);
126
+ return proxy.includes("{url}") ? proxy.replace("{url}", encoded) : `${proxy}${encoded}`;
127
+ }
128
+
41
129
  // src/modules/ambient/oklab.ts
42
130
  var DEG = Math.PI / 180;
43
131
  var LINEAR_LUT = new Float32Array(256);
@@ -132,91 +220,6 @@ function onColorFor(background, minContrast = 4.5) {
132
220
  return useLight ? { c: 0, h: 0, l: 1 } : { c: 0, h: 0, l: 0 };
133
221
  }
134
222
 
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
223
  // src/modules/ambient/quantize.ts
221
224
  function kmeans(points, weights, requested, { iterations = 16, lightnessWeight = 0.45 } = {}) {
222
225
  const n = weights.length;
@@ -290,6 +293,7 @@ function kmeans(points, weights, requested, { iterations = 16, lightnessWeight =
290
293
  }
291
294
 
292
295
  // src/modules/ambient/analyze.ts
296
+ var AMBIENT_ANALYSIS_VERSION = 2;
293
297
  var MIN_CHROMA = 0.022;
294
298
  var MIN_PRESENCE = 0.03;
295
299
  var MIN_POPULATION = 4e-3;
@@ -346,26 +350,47 @@ function pickBest(items, score) {
346
350
  return best;
347
351
  }
348
352
  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
+ function skinLikeness({ c, h, l }) {
354
+ return (1 - smoothstep(12, 26, hueDistance(h, 54))) * smoothstep(0.02, 0.035, c) * (1 - smoothstep(0.11, 0.15, c)) * smoothstep(0.26, 0.38, l) * (1 - smoothstep(0.88, 0.95, l));
355
+ }
356
+ var warmth = (h) => 1 - smoothstep(22, 50, hueDistance(h, 42));
357
+ var salience = (color) => 1 - 0.65 * skinLikeness(color);
358
+ var roleChroma = (source, { cap, gain }) => Math.min(
359
+ cap * (1 - 0.3 * warmth(source.h)),
360
+ // A faint tint is raised further, so it still reads as a colour.
361
+ Math.max(source.c * gain, Math.min(0.05, source.c * 1.6)) * (1 - 0.3 * skinLikeness(source))
362
+ );
363
+ var fitTone = (h, l, c) => ({
364
+ c: Math.max(0, Math.min(c, maxChroma(l, h) * 0.96)),
365
+ h,
366
+ l
367
+ });
368
+ function buildSwatch(tone, population, fromImage, minContrast) {
369
+ const room = maxChroma(tone.l, tone.h) * 0.96;
370
+ const rel = room > 0 ? tone.c / room : 0;
353
371
  const ramp = {};
354
372
  for (const step of AMBIENT_TONES) {
355
- ramp[step] = formatOklch(toneOf(h, step / 100, rel, cap));
373
+ ramp[step] = formatOklch(toneOf(tone.h, step / 100, rel, Math.max(tone.c, 0.04)));
356
374
  }
357
375
  return {
358
376
  c: round3(tone.c),
359
377
  color: formatOklch(tone),
360
378
  fromImage,
361
- h: Math.round(h * 10) / 10,
379
+ h: Math.round(tone.h * 10) / 10,
362
380
  l: round3(tone.l),
363
381
  on: formatOklch(onColorFor(tone, minContrast)),
364
- population: round3(source?.pop ?? 0),
382
+ population: round3(population),
365
383
  ramp
366
384
  };
367
385
  }
368
- var lift = (rel, base, gain, ceiling) => clamp(base + gain * rel, 0, ceiling);
386
+ function roleTone(source, spec, fallback, dilution = 1) {
387
+ if (!source) return fitTone(fallback.h, clamp(fallback.l, spec.min, spec.max), fallback.c);
388
+ return fitTone(
389
+ source.h,
390
+ clamp(source.l, spec.min, spec.max),
391
+ roleChroma(source, spec) * dilution
392
+ );
393
+ }
369
394
  function clusterAmbientPixels(pixels, options) {
370
395
  const { data, width, height } = pixels;
371
396
  const bounds = options.letterbox ? findContentBounds(pixels) : { bottom: height, left: 0, right: width, top: 0 };
@@ -437,18 +462,23 @@ function clusterAmbientPixels(pixels, options) {
437
462
  points[i * 3 + 2] = b;
438
463
  weights[i] = w * (1 + 5 * Math.sqrt(a * a + b * b));
439
464
  });
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]);
465
+ const { assignment, k } = kmeans(points, weights, budgets[group]);
466
+ const sums = new Float64Array(k * 4);
467
+ members.forEach((bin, i) => {
468
+ for (let channel = 0; channel < 4; channel++) {
469
+ sums[assignment[i] * 4 + channel] += binSums[bin * 4 + channel];
470
+ }
471
+ });
443
472
  const index = new Int32Array(k).fill(-1);
444
473
  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]]);
474
+ const pop = sums[j * 4 + 3];
475
+ if (pop <= 0) continue;
476
+ const lch = labToLch([sums[j * 4] / pop, sums[j * 4 + 1] / pop, sums[j * 4 + 2] / pop]);
447
477
  const room = maxChroma(lch.l, lch.h);
448
478
  index[j] = clusters.length;
449
479
  clusters.push({
450
480
  ...lch,
451
- pop: pop[j] / total,
481
+ pop: pop / total,
452
482
  rel: room > 0 ? clamp(lch.c / room, 0, 1) : 0,
453
483
  rely: reliability(lch.l)
454
484
  });
@@ -456,20 +486,26 @@ function clusterAmbientPixels(pixels, options) {
456
486
  members.forEach((bin, i) => clusterOfBin[bin] = index[assignment[i]]);
457
487
  });
458
488
  const regionWeights = AMBIENT_REGIONS.map(() => new Float64Array(clusters.length));
489
+ const regionLab = AMBIENT_REGIONS.map(() => new Float64Array(clusters.length * 3));
459
490
  for (let i = 0; i < pixelBins.length; i++) {
460
491
  const bin = pixelBins[i];
461
492
  if (bin < 0) continue;
462
493
  const cluster = clusterOfBin[bin];
463
494
  const bits = pixelRegions[i];
495
+ const w = binSums[bin * 4 + 3];
464
496
  AMBIENT_REGIONS.forEach((region, r) => {
465
- if (bits & REGION_BITS[region]) regionWeights[r][cluster] += 1;
497
+ if (!(bits & REGION_BITS[region])) return;
498
+ regionWeights[r][cluster] += 1;
499
+ for (let channel = 0; channel < 3; channel++) {
500
+ regionLab[r][cluster * 3 + channel] += binSums[bin * 4 + channel] / w;
501
+ }
466
502
  });
467
503
  }
468
- return { clusters, meanL, regionWeights };
504
+ return { clusters, meanL, regionLab, regionWeights };
469
505
  }
470
506
  function analyzeAmbientPixels(pixels, overrides = {}) {
471
507
  const options = { ...DEFAULT_ANALYSIS, ...overrides };
472
- const { clusters, meanL, regionWeights } = clusterAmbientPixels(pixels, options);
508
+ const { clusters, meanL, regionLab, regionWeights } = clusterAmbientPixels(pixels, options);
473
509
  const chromatic = clusters.filter(isChromatic);
474
510
  const presence = chromatic.reduce(
475
511
  (sum, cluster) => sum + cluster.pop * colorfulness(cluster.c) * cluster.rely,
@@ -478,7 +514,7 @@ function analyzeAmbientPixels(pixels, overrides = {}) {
478
514
  const neutral = presence < MIN_PRESENCE;
479
515
  const dilution = 0.55 + 0.45 * Math.min(1, presence / 0.25);
480
516
  const { minContrast, minHueDistance } = options;
481
- const grey = { h: 0, l: clamp(meanL, 0.3, 0.8) };
517
+ const grey = { c: 0, h: 0, l: clamp(meanL, 0.3, 0.8) };
482
518
  const family = /* @__PURE__ */ new Map();
483
519
  for (const cluster of chromatic) {
484
520
  let share2 = 0;
@@ -488,69 +524,62 @@ function analyzeAmbientPixels(pixels, overrides = {}) {
488
524
  family.set(cluster, share2);
489
525
  }
490
526
  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);
527
+ const dominantPick = neutral ? null : pickBest(
528
+ chromatic,
529
+ (cluster) => share(cluster) * cluster.c * cluster.rely * salience(cluster)
530
+ );
493
531
  const vividPick = neutral ? null : pickBest(
494
532
  chromatic.filter((cluster) => share(cluster) >= MIN_POPULATION),
495
- (cluster) => cluster.c * cluster.c * Math.pow(share(cluster), 0.3) * cluster.rely
533
+ (cluster) => cluster.c * cluster.c * Math.pow(share(cluster), 0.3) * cluster.rely * salience(cluster)
496
534
  );
497
535
  const accentPick = pickBest(
498
536
  chromatic.filter(
499
537
  (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
538
  ),
501
- (cluster) => Math.pow(share(cluster), 0.6) * cluster.c * cluster.rely
539
+ (cluster) => Math.pow(share(cluster), 0.6) * cluster.c * cluster.rely * salience(cluster)
502
540
  );
503
541
  const mutedPick = neutral ? null : pickBest(
504
542
  chromatic.filter((cluster) => cluster.c <= 0.09),
505
543
  (cluster) => share(cluster) * cluster.rely * (1 - cluster.c / 0.12)
506
544
  );
507
545
  const dominantHue = dominantPick?.h ?? vividPick?.h ?? 0;
546
+ const accentSource = accentPick ?? vividPick;
508
547
  const roles = {
509
548
  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,
549
+ roleTone(dominantPick, { cap: 0.2, gain: 1.15, max: 0.74, min: 0.42 }, grey),
550
+ dominantPick ? share(dominantPick) : 0,
518
551
  true,
519
552
  minContrast
520
553
  ),
521
554
  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 },
555
+ roleTone(
556
+ vividPick,
557
+ { cap: 0.22, gain: 1.35, max: 0.84, min: 0.62 },
558
+ { c: 0, h: dominantHue, l: 0.78 },
559
+ dilution
560
+ ),
561
+ vividPick ? share(vividPick) : 0,
530
562
  true,
531
563
  minContrast
532
564
  ),
533
565
  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) },
566
+ roleTone(
567
+ mutedPick,
568
+ { cap: 0.07, gain: 1, max: 0.66, min: 0.38 },
569
+ { c: dominantPick ? 0.035 : 0, h: dominantHue, l: clamp(meanL, 0.38, 0.66) }
570
+ ),
571
+ mutedPick ? share(mutedPick) : 0,
542
572
  Boolean(mutedPick) || neutral,
543
573
  minContrast
544
574
  ),
545
575
  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 },
576
+ roleTone(
577
+ accentSource,
578
+ { cap: 0.22, gain: 1.3, max: 0.8, min: 0.55 },
579
+ { c: 0, h: dominantHue, l: 0.72 },
580
+ dilution
581
+ ),
582
+ accentSource ? share(accentSource) : 0,
554
583
  Boolean(accentPick),
555
584
  minContrast
556
585
  )
@@ -567,35 +596,39 @@ function analyzeAmbientPixels(pixels, overrides = {}) {
567
596
  options.shadowLightness - 0.02,
568
597
  options.shadowLightness + 0.02
569
598
  );
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 };
599
+ const shadow = deepSource ? toneOf(deepSource.h, shadowL, clamp(0.3 + 0.7 * deepSource.rel, 0, 0.85), 0.1) : { c: 0, h: 0, l: shadowL };
571
600
  const regions = {};
572
601
  AMBIENT_REGIONS.forEach((region, r) => {
573
602
  const weights = regionWeights[r];
603
+ const lab = regionLab[r];
574
604
  let regionTotal = 0;
575
605
  let regionL = 0;
576
606
  clusters.forEach((cluster, j) => {
577
607
  regionTotal += weights[j];
578
608
  regionL += cluster.l * weights[j];
579
609
  });
580
- const share2 = (j) => regionTotal > 0 ? weights[j] / regionTotal : 0;
581
610
  let strength = 0;
582
611
  let pick = null;
583
612
  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;
613
+ clusters.forEach((_, j) => {
614
+ const count = weights[j];
615
+ if (count <= 0) return;
616
+ const local = labToLch([lab[j * 3] / count, lab[j * 3 + 1] / count, lab[j * 3 + 2] / count]);
617
+ const rely = reliability(local.l);
618
+ if (local.c < MIN_CHROMA || rely <= 0) return;
619
+ const part = count / regionTotal;
620
+ strength += part * colorfulness(local.c) * rely;
621
+ const score = part * local.c * rely * salience(local);
588
622
  if (score > pickScore) {
589
623
  pickScore = score;
590
- pick = cluster;
624
+ pick = local;
591
625
  }
592
626
  });
593
627
  const chosen = neutral || strength < MIN_PRESENCE ? null : pick;
594
- const tone = chosen ? toneOf(
628
+ const tone = chosen ? fitTone(
595
629
  chosen.h,
596
630
  clamp(chosen.l, 0.42, 0.72),
597
- lift(chosen.rel, 0.25, 0.85, 0.9),
598
- 0.22
631
+ roleChroma(chosen, { cap: 0.18, gain: 1.15 })
599
632
  ) : {
600
633
  c: 0,
601
634
  h: 0,
@@ -896,20 +929,63 @@ function normalizeColorMap(colors) {
896
929
  }
897
930
  return result;
898
931
  }
899
- function applyScopedCssVariables(element, varMap) {
900
- const previous = /* @__PURE__ */ new Map();
932
+ var layersOf = /* @__PURE__ */ new WeakMap();
933
+ function paint(element, state, names) {
934
+ for (const name of names) {
935
+ let value;
936
+ for (let i = state.layers.length - 1; i >= 0 && value === void 0; i--) {
937
+ value = state.layers[i].vars[name];
938
+ }
939
+ if (value !== void 0) {
940
+ element.style.setProperty(name, value);
941
+ continue;
942
+ }
943
+ const base = state.base.get(name);
944
+ if (base?.value) element.style.setProperty(name, base.value, base.priority);
945
+ else element.style.removeProperty(name);
946
+ }
947
+ }
948
+ function removeLayer(element, state, layer) {
949
+ const index = state.layers.indexOf(layer);
950
+ if (index < 0) return;
951
+ if (layer.leaving) clearTimeout(layer.leaving);
952
+ state.layers.splice(index, 1);
953
+ paint(element, state, Object.keys(layer.vars));
954
+ if (state.layers.length === 0) layersOf.delete(element);
955
+ }
956
+ function applyScopedCssVariables(element, varMap, { linger = 0 } = {}) {
957
+ const vars = {};
901
958
  for (const [name, value] of Object.entries(varMap)) {
902
- if (!name || !value) continue;
903
- previous.set(name, {
959
+ if (name && value) vars[name] = value;
960
+ }
961
+ let state = layersOf.get(element);
962
+ if (!state) {
963
+ state = { base: /* @__PURE__ */ new Map(), layers: [] };
964
+ layersOf.set(element, state);
965
+ }
966
+ for (const layer2 of state.layers.filter((layer3) => layer3.leaving)) {
967
+ removeLayer(element, state, layer2);
968
+ }
969
+ if (!layersOf.has(element)) layersOf.set(element, state);
970
+ for (const name of Object.keys(vars)) {
971
+ if (state.base.has(name)) continue;
972
+ state.base.set(name, {
904
973
  priority: element.style.getPropertyPriority(name),
905
974
  value: element.style.getPropertyValue(name)
906
975
  });
907
- element.style.setProperty(name, value);
908
976
  }
977
+ const layer = { leaving: null, vars };
978
+ state.layers.push(layer);
979
+ paint(element, state, Object.keys(vars));
980
+ const owner = state;
981
+ let released = false;
909
982
  return () => {
910
- for (const [name, { priority, value }] of previous) {
911
- if (value) element.style.setProperty(name, value, priority);
912
- else element.style.removeProperty(name);
983
+ if (released) return;
984
+ released = true;
985
+ if (linger > 0 && element.isConnected) {
986
+ layer.leaving = setTimeout(() => removeLayer(element, owner, layer), linger);
987
+ } else {
988
+ removeLayer(element, owner, layer);
913
989
  }
914
990
  };
915
991
  }
@@ -983,6 +1059,7 @@ function useAmbientTheme(config = null) {
983
1059
  cssVars = null,
984
1060
  image = null,
985
1061
  initialPalette = null,
1062
+ linger,
986
1063
  options = null,
987
1064
  scope: scope2 = null,
988
1065
  transition = true
@@ -1028,14 +1105,16 @@ function useAmbientTheme(config = null) {
1028
1105
  if (transition) {
1029
1106
  targetEl.classList.add(...transitionKey.split(" ").filter(Boolean));
1030
1107
  }
1031
- const restoreVariables = applyScopedCssVariables(targetEl, resolvedVarMap);
1108
+ const restoreVariables = applyScopedCssVariables(targetEl, resolvedVarMap, {
1109
+ linger: linger ?? (scope2 ? 0 : getAmbientSettings().linger)
1110
+ });
1032
1111
  return () => {
1033
1112
  restoreVariables();
1034
1113
  if (transition && scope2) {
1035
1114
  targetEl.classList.remove(...transitionKey.split(" ").filter(Boolean));
1036
1115
  }
1037
1116
  };
1038
- }, [resolvedVarMap, scope2, transition, transitionKey]);
1117
+ }, [linger, resolvedVarMap, scope2, transition, transitionKey]);
1039
1118
  return { isExtracting, palette: extractedPalette };
1040
1119
  }
1041
1120
  function useAmbient(configOrImage) {
@@ -1134,6 +1213,6 @@ function defineAmbient(definition = {}) {
1134
1213
  });
1135
1214
  }
1136
1215
 
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
1216
+ export { AMBIENT_ANALYSIS_VERSION, AMBIENT_REGIONS, AMBIENT_ROLES, AMBIENT_TONES, AmbientContext, AmbientProvider, DEFAULT_ANALYSIS, DEFAULT_CSS_VAR_NAMES, DEFAULT_LINGER_MS, ambientModule, ambientTheme, analyzeAmbientPixels, applyScopedCssVariables, configureAmbient, contrastRatio, defineAmbient, extractPaletteFromImage, findContentBounds, formatOklch, getAmbientSettings, getCachedAmbientPalette, maxChroma, normalizeColorMap, onColorFor, paletteVarMap, primeAmbientPalette, resolveAmbientVarMap, resolveCssVarNames, resolvePageAmbientTheme, resolveTargetElement, skinLikeness, srgbToOklab, toneOf, useAmbient, useAmbientColor, useAmbientTheme };
1217
+ //# sourceMappingURL=chunk-TG6NMHXI.js.map
1218
+ //# sourceMappingURL=chunk-TG6NMHXI.js.map