@yewdiel21/motion-kit 0.14.0 → 0.15.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 (154) hide show
  1. package/README.md +174 -155
  2. package/bin/motion-kit.mjs +3 -3
  3. package/dist/auto/index.d.ts +6 -3
  4. package/dist/auto/index.js +8 -2
  5. package/dist/auto/index.js.map +1 -1
  6. package/dist/{chunk-EY4BJKC4.js → chunk-6IRJYXJU.js} +2 -2
  7. package/dist/{chunk-CU6SXW3E.js → chunk-A6QQCF6V.js} +1309 -310
  8. package/dist/chunk-A6QQCF6V.js.map +1 -0
  9. package/dist/chunk-JXRS3OEA.js +3638 -0
  10. package/dist/chunk-JXRS3OEA.js.map +1 -0
  11. package/dist/css/components/add-to-cart.css +72 -0
  12. package/dist/css/components/bidirectional-slider.css +168 -0
  13. package/dist/css/components/data-table.css +121 -26
  14. package/dist/css/components/filter-builder.css +10 -15
  15. package/dist/css/components/heatmap-chart.css +391 -0
  16. package/dist/css/components/hover-card.css +8 -2
  17. package/dist/css/components/image-preview.css +90 -2
  18. package/dist/css/components/json-viewer.css +33 -1
  19. package/dist/css/components/nav.css +102 -102
  20. package/dist/css/components/orbit-menu.css +130 -0
  21. package/dist/css/components/pagination.css +10 -5
  22. package/dist/css/components/pie-chart.css +390 -0
  23. package/dist/css/components/pill-button.css +135 -0
  24. package/dist/css/components/radar-chart.css +370 -0
  25. package/dist/css/components/radial-chart.css +398 -0
  26. package/dist/css/components/sankey-chart.css +358 -0
  27. package/dist/css/components/scroll-island.css +111 -0
  28. package/dist/css/components/select.css +136 -0
  29. package/dist/css/components/toolbar.css +122 -0
  30. package/dist/css/components/tree-view.css +79 -0
  31. package/dist/css/components/voice-dots.css +70 -0
  32. package/dist/elements/index.d.ts +180 -7
  33. package/dist/elements/index.js +287 -6
  34. package/dist/elements/index.js.map +1 -1
  35. package/dist/{engine-Cn6IhE3z.d.ts → engine-5zRIssTO.d.ts} +11 -1
  36. package/dist/index.css +1832 -168
  37. package/dist/index.d.ts +4 -4
  38. package/dist/index.js +33 -3
  39. package/dist/index.js.map +1 -1
  40. package/dist/react/index.d.ts +117 -11
  41. package/dist/react/index.js +314 -13
  42. package/dist/react/index.js.map +1 -1
  43. package/dist/{reduced-BBUusld6.d.ts → reduced-6X_bns60.d.ts} +1 -1
  44. package/dist/{reveal-DEvp6U6T.d.ts → reveal-BfyKKsw4.d.ts} +97 -4
  45. package/dist/store-CA2Qcun_.d.ts +453 -0
  46. package/package.json +9 -16
  47. package/registry.json +1096 -948
  48. package/src/auto/auto-init.ts +6 -0
  49. package/src/components/add-to-cart/README.md +34 -0
  50. package/src/components/add-to-cart/add-to-cart.css +71 -0
  51. package/src/components/add-to-cart/element.ts +20 -0
  52. package/src/components/add-to-cart/engine.ts +206 -0
  53. package/src/components/add-to-cart/react.tsx +29 -0
  54. package/src/components/bidirectional-slider/README.md +46 -0
  55. package/src/components/bidirectional-slider/bidirectional-slider.css +167 -0
  56. package/src/components/bidirectional-slider/element.ts +42 -0
  57. package/src/components/bidirectional-slider/engine.ts +289 -0
  58. package/src/components/bidirectional-slider/react.tsx +45 -0
  59. package/src/components/chart/README.md +10 -0
  60. package/src/components/chart/element.ts +2 -0
  61. package/src/components/chart/engine.ts +50 -23
  62. package/src/components/chart/parts.ts +237 -0
  63. package/src/components/chart/react.tsx +2 -2
  64. package/src/components/chart/use-chart.ts +55 -0
  65. package/src/components/data-table/README.md +3 -3
  66. package/src/components/data-table/data-table.css +121 -26
  67. package/src/components/data-table/engine.ts +81 -29
  68. package/src/components/editable-table/engine.ts +67 -11
  69. package/src/components/filter-builder/engine.ts +33 -2
  70. package/src/components/filter-builder/filter-builder.css +10 -15
  71. package/src/components/heatmap-chart/README.md +48 -0
  72. package/src/components/heatmap-chart/element.ts +21 -0
  73. package/src/components/heatmap-chart/engine.ts +281 -0
  74. package/src/components/heatmap-chart/heatmap-chart.css +93 -0
  75. package/src/components/heatmap-chart/react.tsx +14 -0
  76. package/src/components/hover-card/engine.ts +30 -1
  77. package/src/components/hover-card/hover-card.css +8 -2
  78. package/src/components/image-preview/element.ts +1 -1
  79. package/src/components/image-preview/engine.ts +95 -15
  80. package/src/components/image-preview/image-preview.css +90 -2
  81. package/src/components/image-preview/react.tsx +3 -3
  82. package/src/components/json-viewer/engine.ts +11 -2
  83. package/src/components/json-viewer/json-viewer.css +33 -1
  84. package/src/components/nav/README.md +12 -10
  85. package/src/components/nav/element.ts +5 -5
  86. package/src/components/nav/engine.ts +21 -14
  87. package/src/components/nav/nav.css +102 -102
  88. package/src/components/nav/react.tsx +4 -4
  89. package/src/components/orbit-menu/README.md +46 -0
  90. package/src/components/orbit-menu/element.ts +27 -0
  91. package/src/components/orbit-menu/engine.ts +179 -0
  92. package/src/components/orbit-menu/orbit-menu.css +129 -0
  93. package/src/components/orbit-menu/react.tsx +44 -0
  94. package/src/components/pagination/README.md +1 -1
  95. package/src/components/pagination/engine.ts +50 -4
  96. package/src/components/pagination/pagination.css +10 -5
  97. package/src/components/pie-chart/README.md +48 -0
  98. package/src/components/pie-chart/element.ts +30 -0
  99. package/src/components/pie-chart/engine.ts +283 -0
  100. package/src/components/pie-chart/pie-chart.css +92 -0
  101. package/src/components/pie-chart/react.tsx +14 -0
  102. package/src/components/pill-button/README.md +46 -0
  103. package/src/components/pill-button/element.ts +16 -0
  104. package/src/components/pill-button/engine.ts +77 -0
  105. package/src/components/pill-button/pill-button.css +134 -0
  106. package/src/components/pill-button/react.tsx +32 -0
  107. package/src/components/radar-chart/README.md +48 -0
  108. package/src/components/radar-chart/element.ts +30 -0
  109. package/src/components/radar-chart/engine.ts +327 -0
  110. package/src/components/radar-chart/radar-chart.css +72 -0
  111. package/src/components/radar-chart/react.tsx +14 -0
  112. package/src/components/radial-chart/README.md +48 -0
  113. package/src/components/radial-chart/element.ts +30 -0
  114. package/src/components/radial-chart/engine.ts +290 -0
  115. package/src/components/radial-chart/radial-chart.css +100 -0
  116. package/src/components/radial-chart/react.tsx +14 -0
  117. package/src/components/sankey-chart/README.md +48 -0
  118. package/src/components/sankey-chart/element.ts +31 -0
  119. package/src/components/sankey-chart/engine.ts +401 -0
  120. package/src/components/sankey-chart/react.tsx +14 -0
  121. package/src/components/sankey-chart/sankey-chart.css +60 -0
  122. package/src/components/scroll-island/README.md +34 -0
  123. package/src/components/scroll-island/element.ts +16 -0
  124. package/src/components/scroll-island/engine.ts +252 -0
  125. package/src/components/scroll-island/react.tsx +26 -0
  126. package/src/components/scroll-island/scroll-island.css +110 -0
  127. package/src/components/select/README.md +34 -0
  128. package/src/components/select/element.ts +26 -0
  129. package/src/components/select/engine.ts +267 -0
  130. package/src/components/select/react.tsx +33 -0
  131. package/src/components/select/select.css +135 -0
  132. package/src/components/toolbar/README.md +46 -0
  133. package/src/components/toolbar/element.ts +30 -0
  134. package/src/components/toolbar/engine.ts +230 -0
  135. package/src/components/toolbar/react.tsx +34 -0
  136. package/src/components/toolbar/toolbar.css +121 -0
  137. package/src/components/tree-view/element.ts +1 -1
  138. package/src/components/tree-view/engine.ts +174 -0
  139. package/src/components/tree-view/react.tsx +7 -3
  140. package/src/components/tree-view/tree-view.css +79 -0
  141. package/src/components/voice-dots/README.md +48 -0
  142. package/src/components/voice-dots/element.ts +45 -0
  143. package/src/components/voice-dots/engine.ts +243 -0
  144. package/src/components/voice-dots/react.tsx +52 -0
  145. package/src/components/voice-dots/voice-dots.css +69 -0
  146. package/src/elements/index.ts +39 -0
  147. package/src/index.ts +16 -0
  148. package/src/motion/liquid.ts +14 -4
  149. package/src/react/index.ts +14 -1
  150. package/dist/chunk-6P4P7NU6.js +0 -1401
  151. package/dist/chunk-6P4P7NU6.js.map +0 -1
  152. package/dist/chunk-CU6SXW3E.js.map +0 -1
  153. package/dist/store-DjkTBX7f.d.ts +0 -195
  154. /package/dist/{chunk-EY4BJKC4.js.map → chunk-6IRJYXJU.js.map} +0 -0
@@ -0,0 +1,3638 @@
1
+ import {
2
+ EASE,
3
+ Flip,
4
+ addDays,
5
+ bezier,
6
+ blurIn,
7
+ capitalize,
8
+ channels,
9
+ dayKey,
10
+ easeInCubic,
11
+ easeOut,
12
+ easeOutCubic,
13
+ eased,
14
+ flowIn,
15
+ flowInPalette,
16
+ gsap,
17
+ motionSpring,
18
+ press,
19
+ registerMotion,
20
+ revealFrom,
21
+ rollDigits,
22
+ safeFormat,
23
+ splitGraphemes,
24
+ splitWords,
25
+ spring,
26
+ springEase,
27
+ startOfWeek,
28
+ toDate,
29
+ weekdayNames
30
+ } from "./chunk-A6QQCF6V.js";
31
+ import {
32
+ THEMES,
33
+ THEME_EVENT,
34
+ getTheme,
35
+ setTheme
36
+ } from "./chunk-RVSKEBIC.js";
37
+ import {
38
+ TIMING,
39
+ createScope,
40
+ entrances,
41
+ reduced,
42
+ subscribe,
43
+ whileVisible
44
+ } from "./chunk-DOOMULVV.js";
45
+ import {
46
+ inert,
47
+ warnOnce
48
+ } from "./chunk-TGV27ZWM.js";
49
+
50
+ // src/motion/chat.ts
51
+ var isUser = (bubble) => bubble.dataset.mkRole === "user" || bubble.classList.contains("user");
52
+ function enterMessages(bubbles, { textSelector = "[data-mk-text], .message-text" } = {}) {
53
+ if (!bubbles.length || reduced()) return null;
54
+ registerMotion();
55
+ const reverts = [];
56
+ const cleanup = () => {
57
+ for (const revert of reverts.splice(0)) revert();
58
+ };
59
+ const timeline = gsap.timeline({ onComplete: cleanup, onInterrupt: cleanup });
60
+ bubbles.forEach((bubble, index) => {
61
+ gsap.set(bubble, { transformOrigin: isUser(bubble) ? "100% 50%" : "0% 50%" });
62
+ timeline.fromTo(
63
+ bubble,
64
+ { scale: 0.5, autoAlpha: 0, filter: "blur(24px)" },
65
+ { scale: 1, autoAlpha: 1, filter: "blur(0px)", duration: TIMING.bubble, ease: EASE.signature, clearProps: "transform,opacity,visibility,filter" },
66
+ index * TIMING.bubbleStagger
67
+ );
68
+ const text = bubble.querySelector(textSelector);
69
+ if (!text) return;
70
+ const before = text.offsetHeight;
71
+ const split = splitWords(text);
72
+ const lineHeight = parseFloat(getComputedStyle(text).lineHeight) || 0;
73
+ let targets = split.parts;
74
+ if (!split.parts.length || text.offsetHeight - before > lineHeight / 2) {
75
+ split.revert();
76
+ targets = [text];
77
+ } else reverts.push(() => split.revert());
78
+ timeline.fromTo(
79
+ targets,
80
+ { y: 16, autoAlpha: 0, filter: "blur(6px)" },
81
+ {
82
+ y: 0,
83
+ autoAlpha: 1,
84
+ filter: "blur(0px)",
85
+ duration: TIMING.word,
86
+ ease: "back.out(1)",
87
+ stagger: TIMING.wordStagger,
88
+ clearProps: "transform,opacity,visibility,filter"
89
+ },
90
+ index * TIMING.bubbleStagger
91
+ );
92
+ });
93
+ return timeline;
94
+ }
95
+ function fitComposer(field, min = 64) {
96
+ const from = field.offsetHeight;
97
+ field.style.height = "auto";
98
+ const to = Math.max(min, field.scrollHeight);
99
+ field.style.height = `${from}px`;
100
+ if (from === to || reduced()) {
101
+ field.style.height = `${to}px`;
102
+ return null;
103
+ }
104
+ registerMotion();
105
+ return gsap.to(field, { height: to, duration: TIMING.composer, ease: "power2.out", overwrite: "auto" });
106
+ }
107
+ function scrollToLatest(scroller) {
108
+ const top = scroller.scrollHeight - scroller.clientHeight;
109
+ if (reduced()) {
110
+ scroller.scrollTop = top;
111
+ return null;
112
+ }
113
+ registerMotion();
114
+ const state = { top: scroller.scrollTop };
115
+ return gsap.to(state, {
116
+ top,
117
+ duration: TIMING.chatScroll,
118
+ ease: EASE.chatScroll,
119
+ overwrite: "auto",
120
+ onUpdate: () => {
121
+ scroller.scrollTop = state.top;
122
+ }
123
+ });
124
+ }
125
+
126
+ // src/components/theme-picker/engine.ts
127
+ function createThemePicker(root, options = {}) {
128
+ if (!root) {
129
+ warnOnce("theme-picker: missing root");
130
+ return inert({ select() {
131
+ } });
132
+ }
133
+ registerMotion();
134
+ const scope = createScope();
135
+ const ctx = gsap.context(() => {
136
+ });
137
+ const generated = [];
138
+ let swatches = [...root.querySelectorAll("[data-mk-swatch]")];
139
+ if (!swatches.length) {
140
+ const byId = new Map(THEMES.map((theme) => [theme.id, theme]));
141
+ const ids = options.themes ?? THEMES.filter((theme) => options.showHidden || !theme.hidden).map((theme) => theme.id);
142
+ for (const id of ids) {
143
+ const theme = byId.get(id);
144
+ if (!theme) {
145
+ warnOnce(`theme-picker: unknown theme "${id}"`);
146
+ continue;
147
+ }
148
+ const button = document.createElement("button");
149
+ button.type = "button";
150
+ button.className = "mk-swatch";
151
+ button.dataset.mkSwatch = theme.id;
152
+ button.title = theme.name;
153
+ button.setAttribute("aria-label", theme.name);
154
+ button.style.setProperty("--mk-swatch", theme.seed);
155
+ root.append(button);
156
+ generated.push(button);
157
+ }
158
+ swatches = generated;
159
+ }
160
+ scope.attr(root, "role", "group");
161
+ if (!root.hasAttribute("aria-label")) scope.attr(root, "aria-label", options.labels?.group ?? "Tema");
162
+ const sync = () => {
163
+ const current = getTheme().id;
164
+ for (const swatch of swatches) scope.attr(swatch, "aria-pressed", String(swatch.dataset.mkSwatch === current));
165
+ };
166
+ const select = (id) => {
167
+ setTheme(id);
168
+ options.onChange?.(id);
169
+ };
170
+ scope.on(root, "click", (event) => {
171
+ const swatch = event.target?.closest("[data-mk-swatch]");
172
+ if (swatch && root.contains(swatch) && swatch.dataset.mkSwatch) select(swatch.dataset.mkSwatch);
173
+ });
174
+ scope.on(document, THEME_EVENT, sync);
175
+ sync();
176
+ if (entrances()) ctx.add(() => flowInPalette(swatches));
177
+ root.dataset.mkReady = "";
178
+ return {
179
+ select,
180
+ destroy() {
181
+ scope.dispose();
182
+ ctx.revert();
183
+ for (const button of generated.splice(0)) button.remove();
184
+ delete root.dataset.mkReady;
185
+ }
186
+ };
187
+ }
188
+
189
+ // src/components/toast/store.ts
190
+ var TOAST_LIMIT = 5;
191
+ var items = [];
192
+ var nextId = 1;
193
+ var listeners = /* @__PURE__ */ new Set();
194
+ var emit = () => {
195
+ for (const listener of listeners) listener();
196
+ };
197
+ var getToasts = () => items;
198
+ function subscribeToasts(listener) {
199
+ listeners.add(listener);
200
+ return () => {
201
+ listeners.delete(listener);
202
+ };
203
+ }
204
+ function show(message, { origin = null, duration = 4e3, tone = "success", action, icon = true } = {}) {
205
+ const id = nextId++;
206
+ const live = items.filter((item) => !item.closing);
207
+ const extra = new Set(live.slice(0, Math.max(0, live.length - TOAST_LIMIT + 1)).map((item) => item.id));
208
+ items = [
209
+ ...items.map((item) => extra.has(item.id) ? { ...item, closing: true } : item),
210
+ { id, message, origin, duration, tone, action: action ?? null, icon, closing: false }
211
+ ];
212
+ emit();
213
+ return id;
214
+ }
215
+ function dismissToast(id) {
216
+ const item = items.find((entry) => entry.id === id);
217
+ if (!item || item.closing) return;
218
+ items = items.map((entry) => entry.id === id ? { ...entry, closing: true } : entry);
219
+ emit();
220
+ }
221
+ function removeToast(id) {
222
+ toastTimers.clear(id);
223
+ items = items.filter((entry) => entry.id !== id);
224
+ emit();
225
+ }
226
+ function clearToasts() {
227
+ for (const item of items) toastTimers.clear(item.id);
228
+ items = [];
229
+ emit();
230
+ }
231
+ var toast = Object.assign(show, { dismiss: dismissToast, clear: clearToasts });
232
+ var running = /* @__PURE__ */ new Map();
233
+ var toastTimers = {
234
+ start(id, duration) {
235
+ if (duration <= 0 || running.has(id)) return;
236
+ running.set(id, { handle: setTimeout(() => dismissToast(id), duration), remaining: duration, started: Date.now() });
237
+ },
238
+ pause(id) {
239
+ const timer = running.get(id);
240
+ if (!timer?.handle) return;
241
+ clearTimeout(timer.handle);
242
+ timer.remaining = Math.max(0, timer.remaining - (Date.now() - timer.started));
243
+ timer.handle = null;
244
+ },
245
+ resume(id) {
246
+ const timer = running.get(id);
247
+ if (!timer || timer.handle) return;
248
+ if (timer.remaining <= 0) {
249
+ dismissToast(id);
250
+ return;
251
+ }
252
+ timer.started = Date.now();
253
+ timer.handle = setTimeout(() => dismissToast(id), timer.remaining);
254
+ },
255
+ clear(id) {
256
+ const timer = running.get(id);
257
+ if (timer?.handle) clearTimeout(timer.handle);
258
+ running.delete(id);
259
+ }
260
+ };
261
+
262
+ // src/components/icons.ts
263
+ var ICON_PATHS = {
264
+ close: "M6 6l12 12M18 6L6 18",
265
+ expand: "M14 4h6v6M10 20H4v-6M20 4l-7 7M4 20l7-7",
266
+ shrink: "M4 14h6v6M20 10h-6V4M10 14l-7 7M14 10l7-7",
267
+ check: "M5 12.5l4.5 4.5L19 7.5",
268
+ alert: "M12 8v5M12 16.5v.01M10.3 3.9L2.6 17.3a2 2 0 001.7 3h15.4a2 2 0 001.7-3L13.7 3.9a2 2 0 00-3.4 0z",
269
+ info: "M12 11v6M12 7.5v.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z",
270
+ send: "M12 19V5M5.5 11.5L12 5l6.5 6.5",
271
+ mic: "M12 3a3 3 0 00-3 3v6a3 3 0 006 0V6a3 3 0 00-3-3zM5.5 11a6.5 6.5 0 0013 0M12 17.5V21",
272
+ sparkle: "M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z",
273
+ grip: "M9 6h.01M15 6h.01M9 12h.01M15 12h.01M9 18h.01M15 18h.01"
274
+ };
275
+ function iconSvg(name, size = 20) {
276
+ return `<svg aria-hidden="true" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${ICON_PATHS[name]}"/></svg>`;
277
+ }
278
+
279
+ // src/components/toast/engine.ts
280
+ var STACK = { peek: 16, scaleStep: 0.05, minScale: 0.7, maxVisible: 3, gap: 8, baseZ: 1e3 };
281
+ var DISTANCE = { enter: 12, enterBlur: 6, exit: 24, exitBlur: 4, fade: 120 };
282
+ var GESTURE = { swipe: 48, wheel: 80, expandDrag: 24, resistance: 0.15, wheelEndMs: 140 };
283
+ var TONE_ICONS = { success: "check", error: "alert", info: "info", neutral: null };
284
+ function layout(count, expanded, heights) {
285
+ let offset = 0;
286
+ return Array.from({ length: count }, (_, index) => {
287
+ if (expanded) {
288
+ const slot = { y: offset, scale: 1, opacity: 1, visible: true, zIndex: STACK.baseZ - index };
289
+ offset += (heights[index] ?? 0) + STACK.gap;
290
+ return slot;
291
+ }
292
+ const visible = index < STACK.maxVisible;
293
+ return {
294
+ y: index * STACK.peek,
295
+ scale: Math.max(STACK.minScale, 1 - index * STACK.scaleStep),
296
+ opacity: visible ? 1 : 0,
297
+ visible,
298
+ zIndex: STACK.baseZ - index
299
+ };
300
+ });
301
+ }
302
+ function revealLetters(element) {
303
+ const text = element.textContent ?? "";
304
+ const T = TIMING.toast;
305
+ if (!text || text.length > 64 || reduced()) return;
306
+ const letters = splitGraphemes(text, 64);
307
+ const original = element.innerHTML;
308
+ const width = element.getBoundingClientRect().width;
309
+ const fragment = document.createDocumentFragment();
310
+ const chars = [];
311
+ let word = null;
312
+ for (const letter of letters) {
313
+ if (!letter.trim()) {
314
+ word = null;
315
+ fragment.append(document.createTextNode(letter));
316
+ continue;
317
+ }
318
+ if (!word) {
319
+ word = document.createElement("span");
320
+ word.style.display = "inline-block";
321
+ word.style.whiteSpace = "nowrap";
322
+ fragment.append(word);
323
+ }
324
+ const char = document.createElement("span");
325
+ char.textContent = letter;
326
+ char.style.display = "inline-block";
327
+ word.append(char);
328
+ chars.push(char);
329
+ }
330
+ if (width) element.style.width = `${width}px`;
331
+ element.textContent = "";
332
+ element.append(fragment);
333
+ gsap.fromTo(
334
+ chars,
335
+ { opacity: 0, yPercent: 40, filter: "blur(4px)" },
336
+ {
337
+ opacity: 1,
338
+ yPercent: 0,
339
+ filter: "blur(0px)",
340
+ duration: T.chars,
341
+ ease: easeOutCubic,
342
+ delay: (index) => T.charsDelay + index * T.charsStagger,
343
+ onComplete: () => {
344
+ element.innerHTML = original;
345
+ element.style.width = "";
346
+ }
347
+ }
348
+ );
349
+ }
350
+ function morphFrom(card, shell, body, origin) {
351
+ const T = TIMING.toast;
352
+ const box = shell.getBoundingClientRect();
353
+ const start = origin.getBoundingClientRect();
354
+ const look = getComputedStyle(origin);
355
+ const own = getComputedStyle(shell);
356
+ const endRadius = parseFloat(own.borderTopLeftRadius) || 20;
357
+ const startRadius = Math.min(parseFloat(look.borderTopLeftRadius) || 0, start.height / 2);
358
+ card.style.width = `${box.width}px`;
359
+ card.style.height = `${box.height}px`;
360
+ body.style.width = `${body.getBoundingClientRect().width}px`;
361
+ const towardX = start.left + start.width / 2 < innerWidth / 2 ? 1 : -1;
362
+ const towardY = start.top + start.height / 2 < innerHeight / 2 ? 1 : -1;
363
+ const offset = { x: start.left - box.left, y: start.top - box.top };
364
+ Object.assign(shell.style, {
365
+ position: "absolute",
366
+ top: "0px",
367
+ left: "0px",
368
+ width: `${start.width}px`,
369
+ height: `${start.height}px`,
370
+ borderRadius: `${startRadius}px`,
371
+ background: look.backgroundColor,
372
+ transform: `translate(${offset.x}px, ${offset.y}px)`,
373
+ transition: "none"
374
+ });
375
+ Object.assign(body.style, { transform: "scale(2)", transformOrigin: "center center", filter: "blur(8px)", opacity: "0" });
376
+ origin.style.transition = "opacity 160ms ease";
377
+ origin.style.opacity = "0";
378
+ void shell.offsetWidth;
379
+ shell.style.transition = `background ${T.morphColor}s ease-out ${T.morphColorDelay}s, box-shadow ${T.morphShadow}s ease-out ${T.morphShadowDelay}s`;
380
+ shell.style.background = "";
381
+ body.style.transition = `opacity ${T.morphColor}s ease-out ${T.morphColorDelay}s`;
382
+ body.style.opacity = "1";
383
+ const motion = channels(
384
+ { x: offset.x, y: offset.y, width: start.width, height: start.height, radius: startRadius, scale: 2, blur: 8 },
385
+ (name, value) => {
386
+ if (name === "x") offset.x = value;
387
+ else if (name === "y") offset.y = value;
388
+ else if (name === "width") shell.style.width = `${value}px`;
389
+ else if (name === "height") shell.style.height = `${value}px`;
390
+ else if (name === "radius") shell.style.borderRadius = `${value}px`;
391
+ else if (name === "scale") body.style.transform = `scale(${value})`;
392
+ else if (name === "blur") body.style.filter = `blur(${value}px)`;
393
+ if (name === "x" || name === "y") shell.style.transform = `translate(${offset.x}px, ${offset.y}px)`;
394
+ }
395
+ );
396
+ void motion.animate(
397
+ { x: 0, y: 0, width: box.width, height: box.height, radius: endRadius, scale: 1, blur: 0 },
398
+ {
399
+ x: spring(144, 14, { velocity: 2400, direction: towardX }),
400
+ y: spring(144, 14, { velocity: 2400, direction: towardY }),
401
+ width: eased(T.morphSize, bezier(0.8, 0.3, 0.5, 0.8)),
402
+ height: eased(T.morphSize, bezier(0.8, 0.3, 0.5, 0.8)),
403
+ radius: eased(T.morphRadius, easeOut),
404
+ scale: eased(T.morphContent, easeOut),
405
+ blur: eased(T.morphContent, easeOut)
406
+ }
407
+ );
408
+ let settled = false;
409
+ const settle = () => {
410
+ if (settled) return;
411
+ settled = true;
412
+ clearTimeout(timer);
413
+ motion.stop();
414
+ for (const name of ["position", "top", "left", "width", "height", "border-radius", "background", "transform", "transition"]) shell.style.removeProperty(name);
415
+ card.style.removeProperty("width");
416
+ card.style.removeProperty("height");
417
+ for (const name of ["transform", "transform-origin", "filter", "opacity", "width", "transition"]) body.style.removeProperty(name);
418
+ origin.style.transition = "opacity 300ms ease";
419
+ origin.style.opacity = "";
420
+ setTimeout(() => {
421
+ origin.style.transition = "";
422
+ }, 320);
423
+ };
424
+ const timer = setTimeout(settle, T.safetyMs);
425
+ return settle;
426
+ }
427
+ function buildCard(item, dismissLabel) {
428
+ const card = document.createElement("div");
429
+ card.className = "mk-toast";
430
+ card.dataset.mkToast = String(item.id);
431
+ card.dataset.mkTone = item.tone;
432
+ const shell = document.createElement("div");
433
+ shell.className = "mk-toast__shell";
434
+ const body = document.createElement("div");
435
+ body.className = "mk-toast__body";
436
+ const icon = item.icon ? TONE_ICONS[item.tone] : null;
437
+ if (icon) {
438
+ const holder = document.createElement("span");
439
+ holder.className = "mk-toast__icon";
440
+ holder.innerHTML = iconSvg(icon, 18);
441
+ body.append(holder);
442
+ }
443
+ const message = document.createElement("span");
444
+ message.className = "mk-toast__message";
445
+ message.textContent = item.message;
446
+ body.append(message);
447
+ if (item.action) {
448
+ const action = document.createElement("button");
449
+ action.type = "button";
450
+ action.className = "mk-toast__action";
451
+ action.dataset.mkToastAction = "";
452
+ action.textContent = item.action.label;
453
+ body.append(action);
454
+ }
455
+ const dismiss = document.createElement("button");
456
+ dismiss.type = "button";
457
+ dismiss.className = "mk-toast__dismiss";
458
+ dismiss.dataset.mkToastDismiss = "";
459
+ dismiss.setAttribute("aria-label", dismissLabel);
460
+ dismiss.innerHTML = iconSvg("close", 14);
461
+ body.append(dismiss);
462
+ shell.append(body);
463
+ card.append(shell);
464
+ return { card, shell, body, message };
465
+ }
466
+ function createToaster(host, options = {}) {
467
+ if (!host) {
468
+ warnOnce("toaster: missing host");
469
+ return inert();
470
+ }
471
+ registerMotion();
472
+ const dismissLabel = options.labels?.dismiss ?? "Cerrar";
473
+ const scope = createScope();
474
+ const cards = /* @__PURE__ */ new Map();
475
+ const entered = /* @__PURE__ */ new Set();
476
+ const leaving = /* @__PURE__ */ new Set();
477
+ const dragging = /* @__PURE__ */ new Set();
478
+ const settles = /* @__PURE__ */ new Map();
479
+ let expanded = false;
480
+ let collapse;
481
+ const popover = typeof host.showPopover === "function";
482
+ scope.attr(host, "role", "status");
483
+ scope.attr(host, "aria-live", "polite");
484
+ if (popover) scope.attr(host, "popover", "manual");
485
+ const live = () => getToasts().filter((item) => !item.closing).reverse().map((item) => ({ item, entry: cards.get(item.id) })).filter((pair) => pair.entry !== void 0);
486
+ const slotsFor = () => {
487
+ const entries = live();
488
+ return { entries, slots: layout(entries.length, expanded, entries.map(({ entry }) => entry.card.offsetHeight)) };
489
+ };
490
+ const relayout = (duration = TIMING.toast.relayout, ease = easeOutCubic, stagger = 0) => {
491
+ const { entries, slots } = slotsFor();
492
+ entries.forEach(({ item, entry }, index) => {
493
+ const slot = slots[index];
494
+ if (!slot) return;
495
+ entry.card.style.zIndex = String(slot.zIndex);
496
+ entry.card.style.pointerEvents = slot.visible ? "auto" : "none";
497
+ entry.card.setAttribute("aria-hidden", slot.visible ? "false" : "true");
498
+ if (dragging.has(item.id) || !entered.has(item.id)) return;
499
+ gsap.to(entry.card, {
500
+ xPercent: -50,
501
+ y: slot.y,
502
+ scale: slot.scale,
503
+ opacity: slot.opacity,
504
+ duration: reduced() ? 0 : duration,
505
+ ease,
506
+ delay: stagger * index,
507
+ overwrite: "auto"
508
+ });
509
+ });
510
+ };
511
+ const restingY = (id) => {
512
+ const { entries, slots } = slotsFor();
513
+ return slots[entries.findIndex(({ item }) => item.id === id)]?.y ?? 0;
514
+ };
515
+ const openDeck = () => {
516
+ clearTimeout(collapse);
517
+ if (expanded) return;
518
+ expanded = true;
519
+ for (const { item } of live()) toastTimers.pause(item.id);
520
+ relayout(TIMING.toast.expand, springEase(0.55), TIMING.toast.expandStagger);
521
+ };
522
+ const closeDeck = () => {
523
+ clearTimeout(collapse);
524
+ collapse = setTimeout(() => {
525
+ if (!expanded) return;
526
+ expanded = false;
527
+ for (const { item } of live()) toastTimers.resume(item.id);
528
+ relayout(TIMING.toast.expand, springEase(0.55), TIMING.toast.expandStagger);
529
+ }, TIMING.toast.collapseDelayMs);
530
+ };
531
+ const showLayer = (visible) => {
532
+ if (!popover) return;
533
+ try {
534
+ if (host.matches(":popover-open")) host.hidePopover();
535
+ if (visible) host.showPopover();
536
+ } catch {
537
+ }
538
+ };
539
+ const attachGestures = (item, card) => {
540
+ let start = null;
541
+ let wheel = null;
542
+ const move = (offset) => gsap.set(card, { y: restingY(item.id) + offset, opacity: Math.max(0, 1 + Math.min(0, offset) / DISTANCE.fade) });
543
+ const cancel = () => {
544
+ dragging.delete(item.id);
545
+ gsap.to(card, { y: restingY(item.id), opacity: 1, duration: 0.25, ease: easeOutCubic });
546
+ if (!expanded) toastTimers.resume(item.id);
547
+ };
548
+ card.addEventListener("mouseenter", openDeck);
549
+ card.addEventListener("mouseleave", closeDeck);
550
+ card.addEventListener("pointerdown", (event) => {
551
+ if (event.button !== 0 || event.target.closest('button, a, input, textarea, select, [role="button"]')) return;
552
+ start = { y: event.clientY, type: event.pointerType };
553
+ card.setPointerCapture?.(event.pointerId);
554
+ dragging.add(item.id);
555
+ gsap.killTweensOf(card);
556
+ toastTimers.pause(item.id);
557
+ });
558
+ card.addEventListener("pointermove", (event) => {
559
+ if (!start) return;
560
+ const dy = event.clientY - start.y;
561
+ move(dy < 0 ? dy : start.type === "touch" ? dy * GESTURE.resistance : 0);
562
+ });
563
+ const up = (event) => {
564
+ if (!start) return;
565
+ const dy = event.clientY - start.y;
566
+ const type = start.type;
567
+ start = null;
568
+ if (dy < -GESTURE.swipe) {
569
+ dragging.delete(item.id);
570
+ dismissToast(item.id);
571
+ } else if (type === "touch" && dy > GESTURE.expandDrag) {
572
+ cancel();
573
+ if (expanded) closeDeck();
574
+ else openDeck();
575
+ } else cancel();
576
+ };
577
+ card.addEventListener("pointerup", up);
578
+ card.addEventListener("pointercancel", up);
579
+ card.addEventListener(
580
+ "wheel",
581
+ (event) => {
582
+ const delta = event.deltaMode === 1 ? event.deltaY * 16 : event.deltaMode === 2 ? event.deltaY * innerHeight : event.deltaY;
583
+ if (!wheel) {
584
+ wheel = { offset: 0, end: void 0 };
585
+ dragging.add(item.id);
586
+ toastTimers.pause(item.id);
587
+ }
588
+ wheel.offset = Math.min(0, wheel.offset - delta);
589
+ move(wheel.offset);
590
+ clearTimeout(wheel.end);
591
+ wheel.end = setTimeout(() => {
592
+ const offset = wheel?.offset ?? 0;
593
+ wheel = null;
594
+ if (offset < -GESTURE.wheel) {
595
+ dragging.delete(item.id);
596
+ dismissToast(item.id);
597
+ } else cancel();
598
+ }, GESTURE.wheelEndMs);
599
+ },
600
+ { passive: true }
601
+ );
602
+ card.addEventListener("click", (event) => {
603
+ const target = event.target;
604
+ if (target.closest("[data-mk-toast-dismiss]")) dismissToast(item.id);
605
+ else if (target.closest("[data-mk-toast-action]")) {
606
+ item.action?.onClick();
607
+ dismissToast(item.id);
608
+ }
609
+ });
610
+ };
611
+ const finishCard = (id) => {
612
+ settles.get(id)?.();
613
+ settles.delete(id);
614
+ const entry = cards.get(id);
615
+ if (entry) {
616
+ gsap.killTweensOf(entry.card);
617
+ entry.card.remove();
618
+ }
619
+ cards.delete(id);
620
+ entered.delete(id);
621
+ leaving.delete(id);
622
+ dragging.delete(id);
623
+ };
624
+ const render = () => {
625
+ const list = getToasts();
626
+ const ids = new Set(list.map((item) => item.id));
627
+ for (const id of [...cards.keys()]) if (!ids.has(id)) finishCard(id);
628
+ for (const item of list) {
629
+ if (!cards.has(item.id) && !item.closing) {
630
+ const entry = buildCard(item, dismissLabel);
631
+ cards.set(item.id, entry);
632
+ host.append(entry.card);
633
+ gsap.set(entry.card, { xPercent: -50 });
634
+ attachGestures(item, entry.card);
635
+ }
636
+ }
637
+ showLayer(list.some((item) => !item.closing) || leaving.size > 0);
638
+ for (const item of list) {
639
+ const entry = cards.get(item.id);
640
+ if (!entry) continue;
641
+ if (!entered.has(item.id) && !item.closing) {
642
+ entered.add(item.id);
643
+ toastTimers.start(item.id, item.duration);
644
+ relayout();
645
+ gsap.killTweensOf(entry.card);
646
+ gsap.set(entry.card, { xPercent: -50, y: restingY(item.id), scale: 1, opacity: 1 });
647
+ if (expanded) toastTimers.pause(item.id);
648
+ revealLetters(entry.message);
649
+ if (reduced()) continue;
650
+ if (item.origin?.isConnected) settles.set(item.id, morphFrom(entry.card, entry.shell, entry.body, item.origin));
651
+ else {
652
+ const y = restingY(item.id);
653
+ gsap.fromTo(
654
+ entry.card,
655
+ { y: y - DISTANCE.enter, opacity: 0, filter: `blur(${DISTANCE.enterBlur}px)` },
656
+ { y, opacity: 1, filter: "blur(0px)", duration: TIMING.toast.enter, ease: springEase(0.58, -3.5), clearProps: "filter" }
657
+ );
658
+ }
659
+ }
660
+ if (item.closing && !leaving.has(item.id)) {
661
+ leaving.add(item.id);
662
+ toastTimers.clear(item.id);
663
+ entry.card.style.zIndex = String(STACK.baseZ + 1);
664
+ const done = () => {
665
+ finishCard(item.id);
666
+ removeToast(item.id);
667
+ };
668
+ if (reduced()) done();
669
+ else {
670
+ gsap.to(entry.card, {
671
+ y: Number(gsap.getProperty(entry.card, "y")) - DISTANCE.exit,
672
+ opacity: 0,
673
+ filter: `blur(${DISTANCE.exitBlur}px)`,
674
+ duration: TIMING.toast.exit,
675
+ ease: easeInCubic,
676
+ overwrite: "auto",
677
+ onComplete: done
678
+ });
679
+ }
680
+ relayout();
681
+ }
682
+ }
683
+ };
684
+ scope.add(subscribeToasts(render));
685
+ render();
686
+ host.dataset.mkReady = "";
687
+ return {
688
+ destroy() {
689
+ clearTimeout(collapse);
690
+ for (const id of [...cards.keys()]) finishCard(id);
691
+ showLayer(false);
692
+ scope.dispose();
693
+ delete host.dataset.mkReady;
694
+ }
695
+ };
696
+ }
697
+
698
+ // src/components/chat/engine.ts
699
+ var MESSAGE = "[data-mk-message]";
700
+ var PENDING_ID = "mk-chat-pending";
701
+ var part = (root, name) => root.querySelector(`[data-mk-part="${name}"]`);
702
+ var capturePending = (placeholder) => {
703
+ registerMotion();
704
+ return placeholder ? Flip.getState(placeholder) : null;
705
+ };
706
+ function morphPendingToReply(state, reply, scroller) {
707
+ registerMotion();
708
+ if (!state || reduced()) {
709
+ if (!reduced()) enterMessages([reply]);
710
+ return null;
711
+ }
712
+ const text = reply.querySelector("[data-mk-text], .message-text");
713
+ if (text) text.style.width = `${text.getBoundingClientRect().width}px`;
714
+ reply.setAttribute("data-flip-id", PENDING_ID);
715
+ const words = text && !text.querySelector("table, pre") ? splitWords(text) : null;
716
+ if (words?.parts.length) {
717
+ gsap.fromTo(
718
+ words.parts,
719
+ { y: 16, autoAlpha: 0, filter: "blur(6px)" },
720
+ {
721
+ y: 0,
722
+ autoAlpha: 1,
723
+ filter: "blur(0px)",
724
+ duration: TIMING.word,
725
+ ease: "back.out(1)",
726
+ stagger: TIMING.wordStagger,
727
+ clearProps: "transform,opacity,visibility,filter",
728
+ onComplete: () => words.revert()
729
+ }
730
+ );
731
+ }
732
+ return Flip.from(state, {
733
+ targets: reply,
734
+ duration: TIMING.bubble,
735
+ scale: false,
736
+ ease: EASE.signature,
737
+ onUpdate: () => {
738
+ if (scroller) scroller.scrollTop = scroller.scrollHeight;
739
+ },
740
+ onComplete: () => {
741
+ reply.removeAttribute("data-flip-id");
742
+ if (text) text.style.width = "";
743
+ }
744
+ });
745
+ }
746
+ function chatMessage(role, text) {
747
+ const message = document.createElement("div");
748
+ message.dataset.mkMessage = "";
749
+ message.dataset.mkRole = role;
750
+ const body = document.createElement("span");
751
+ body.dataset.mkText = "";
752
+ body.textContent = text;
753
+ message.append(body);
754
+ return message;
755
+ }
756
+ function createChat(root, options = {}) {
757
+ const messages = root ? part(root, "messages") : null;
758
+ if (!root || !messages) {
759
+ warnOnce('chat: needs a [data-mk-part="messages"] container');
760
+ return inert({
761
+ add: () => document.createElement("div"),
762
+ pending() {
763
+ },
764
+ reply: () => document.createElement("div"),
765
+ confirm() {
766
+ },
767
+ setRecording() {
768
+ },
769
+ scrollToLatest() {
770
+ },
771
+ adopt() {
772
+ },
773
+ messages: null
774
+ });
775
+ }
776
+ registerMotion();
777
+ const { clearOnSend = true } = options;
778
+ const thinking = options.labels?.thinking ?? "Pensando...";
779
+ const scope = createScope();
780
+ const composer = part(root, "composer");
781
+ const input = composer?.querySelector('[data-mk-part="input"], textarea') ?? null;
782
+ const mic = part(root, "mic");
783
+ const seen = /* @__PURE__ */ new WeakSet();
784
+ let placeholder = null;
785
+ let pendingTimer;
786
+ let waiting = false;
787
+ scope.attr(messages, "role", "log");
788
+ scope.attr(messages, "aria-live", "polite");
789
+ const existing = [...messages.querySelectorAll(MESSAGE)];
790
+ if (options.animateExisting) enterMessages(existing);
791
+ for (const message of existing) seen.add(message);
792
+ const observer = scope.observe(
793
+ new MutationObserver((records) => {
794
+ const fresh = [];
795
+ for (const record of records) {
796
+ for (const node of record.addedNodes) {
797
+ if (!(node instanceof HTMLElement) || !node.matches(MESSAGE) || seen.has(node)) continue;
798
+ seen.add(node);
799
+ fresh.push(node);
800
+ }
801
+ }
802
+ if (!fresh.length) return;
803
+ enterMessages(fresh);
804
+ scrollToLatest(messages);
805
+ })
806
+ );
807
+ observer.observe(messages, { childList: true });
808
+ const clearPending = () => {
809
+ clearTimeout(pendingTimer);
810
+ pendingTimer = void 0;
811
+ waiting = false;
812
+ };
813
+ const add = (role, text) => {
814
+ const message = chatMessage(role, text);
815
+ messages.append(message);
816
+ return message;
817
+ };
818
+ const pending = () => {
819
+ clearPending();
820
+ waiting = true;
821
+ pendingTimer = setTimeout(() => {
822
+ pendingTimer = void 0;
823
+ if (!waiting || placeholder) return;
824
+ placeholder = document.createElement("div");
825
+ placeholder.dataset.mkMessage = "";
826
+ placeholder.dataset.mkRole = "assistant";
827
+ placeholder.dataset.mkPending = "";
828
+ placeholder.setAttribute("data-flip-id", PENDING_ID);
829
+ placeholder.innerHTML = `<span class="mk-chat__thinking">${iconSvg("sparkle", 18)}<span></span></span>`;
830
+ placeholder.querySelector("span span").textContent = thinking;
831
+ seen.add(placeholder);
832
+ messages.append(placeholder);
833
+ enterMessages([placeholder]);
834
+ scrollToLatest(messages);
835
+ }, TIMING.chat.pendingDelayMs);
836
+ };
837
+ const reply = (content) => {
838
+ const message = typeof content === "string" ? chatMessage("assistant", content) : content;
839
+ clearPending();
840
+ seen.add(message);
841
+ const was = placeholder;
842
+ placeholder = null;
843
+ if (was?.isConnected) {
844
+ const state = capturePending(was);
845
+ was.replaceWith(message);
846
+ morphPendingToReply(state, message, messages);
847
+ } else {
848
+ if (!message.isConnected) messages.append(message);
849
+ enterMessages([message]);
850
+ scrollToLatest(messages);
851
+ }
852
+ return message;
853
+ };
854
+ const confirm = (bar) => {
855
+ if (reduced()) return;
856
+ flowIn(bar);
857
+ const words = splitWords(bar);
858
+ if (!words.parts.length) return;
859
+ gsap.fromTo(
860
+ words.parts,
861
+ { y: 16, autoAlpha: 0, filter: "blur(6px)" },
862
+ {
863
+ y: 0,
864
+ autoAlpha: 1,
865
+ filter: "blur(0px)",
866
+ duration: TIMING.word,
867
+ ease: "back.out(1)",
868
+ stagger: TIMING.chat.confirmWordStagger,
869
+ clearProps: "transform,opacity,visibility,filter",
870
+ onComplete: () => words.revert()
871
+ }
872
+ );
873
+ };
874
+ const send = () => {
875
+ if (!input) return;
876
+ const text = input.value.trim();
877
+ if (!text) return;
878
+ const allowed = root.dispatchEvent(new CustomEvent("mk:send", { bubbles: true, cancelable: true, detail: { text } }));
879
+ if (!allowed) return;
880
+ options.onSend?.(text);
881
+ if (clearOnSend) {
882
+ input.value = "";
883
+ fitComposer(input);
884
+ }
885
+ };
886
+ if (input) {
887
+ scope.on(input, "input", () => fitComposer(input));
888
+ scope.on(input, "keydown", (event) => {
889
+ const key = event;
890
+ if (key.key !== "Enter" || key.shiftKey || key.isComposing) return;
891
+ event.preventDefault();
892
+ send();
893
+ });
894
+ }
895
+ if (composer) {
896
+ scope.on(composer, "submit", (event) => {
897
+ event.preventDefault();
898
+ send();
899
+ });
900
+ const sendButton = part(root, "send");
901
+ if (sendButton && !composer.matches("form")) scope.on(sendButton, "click", send);
902
+ }
903
+ scope.add(() => {
904
+ clearPending();
905
+ placeholder?.remove();
906
+ placeholder = null;
907
+ });
908
+ if (input) fitComposer(input);
909
+ root.dataset.mkReady = "";
910
+ return {
911
+ add,
912
+ pending,
913
+ reply,
914
+ confirm,
915
+ setRecording(on) {
916
+ if (mic) scope.attr(mic, "data-mk-recording", on ? "" : null);
917
+ },
918
+ scrollToLatest: () => scrollToLatest(messages),
919
+ adopt: (message) => seen.add(message),
920
+ messages,
921
+ destroy() {
922
+ scope.dispose();
923
+ if (input) input.style.removeProperty("height");
924
+ delete root.dataset.mkReady;
925
+ }
926
+ };
927
+ }
928
+
929
+ // src/components/nav/engine.ts
930
+ var NAV_STYLES = ["sidebar", "pills", "dock", "card", "rail", "wash", "magnify"];
931
+ var NAV_STYLE_ALIASES = { clasica: "sidebar", bonita: "pills", guapa: "card", tasks: "rail", melon: "wash", tunnel: "magnify" };
932
+ var toNavStyle = (value) => NAV_STYLES.includes(value) ? value : NAV_STYLE_ALIASES[value];
933
+ var ITEM = ".mk-nav__buttons .mk-nav__button";
934
+ var valueOf = (item) => item.dataset.mkValue ?? item.getAttribute("href") ?? void 0;
935
+ var read = (key) => {
936
+ try {
937
+ return localStorage.getItem(key);
938
+ } catch {
939
+ return null;
940
+ }
941
+ };
942
+ var write = (key, value) => {
943
+ try {
944
+ localStorage.setItem(key, value);
945
+ } catch {
946
+ }
947
+ };
948
+ function revealShell(nav) {
949
+ if (!entrances()) return null;
950
+ const shell = nav.closest(".mk-shell") ?? nav.parentElement;
951
+ if (!shell) return null;
952
+ const origin = nav.querySelector(".mk-nav__button--first") ?? nav.querySelector(ITEM) ?? nav;
953
+ return revealFrom(shell, { origin });
954
+ }
955
+ function createNav(root, options = {}) {
956
+ if (!root) {
957
+ warnOnce("nav: missing root");
958
+ return inert({ select() {
959
+ }, setStyle() {
960
+ }, setAccent() {
961
+ }, index: -1, style: "pills", items: [] });
962
+ }
963
+ const scope = createScope();
964
+ const items2 = () => [...root.querySelectorAll(ITEM)];
965
+ const known = [...NAV_STYLES, ...Object.keys(NAV_STYLE_ALIASES)];
966
+ const fromClass = toNavStyle(known.find((name) => root.classList.contains(`mk-nav--${name}`)));
967
+ const saved = options.storageKey ? read(options.storageKey) : null;
968
+ let style = toNavStyle(options.style) ?? toNavStyle(saved) ?? fromClass ?? "pills";
969
+ const paint = (accent2) => {
970
+ const classes = new Set(root.classList);
971
+ for (const name of known) classes.delete(`mk-nav--${name}`);
972
+ classes.add("mk-nav");
973
+ classes.add(`mk-nav--${style}`);
974
+ if (accent2) classes.add("mk-nav--accent");
975
+ else classes.delete("mk-nav--accent");
976
+ scope.attr(root, "class", [...classes].join(" "));
977
+ };
978
+ let accent = options.accent ?? root.classList.contains("mk-nav--accent");
979
+ paint(accent);
980
+ if (root.localName !== "nav") scope.attr(root, "role", "navigation");
981
+ if (!root.hasAttribute("aria-label") && !root.hasAttribute("aria-labelledby")) scope.attr(root, "aria-label", options.labels?.nav ?? "Principal");
982
+ const mark = (current) => {
983
+ for (const item of items2()) scope.attr(item, "aria-current", item === current ? "page" : null);
984
+ };
985
+ const find = (target) => {
986
+ const list = items2();
987
+ return typeof target === "number" ? list[target] : list.find((item) => valueOf(item) === target);
988
+ };
989
+ scope.on(root, "click", (event) => {
990
+ const item = event.target?.closest(ITEM);
991
+ if (!item || !root.contains(item)) return;
992
+ const index = items2().indexOf(item);
993
+ const value = valueOf(item);
994
+ const detail = { index, value, item, originalEvent: event };
995
+ if (!root.dispatchEvent(new CustomEvent("mk:navigate", { bubbles: true, cancelable: true, detail }))) {
996
+ event.preventDefault();
997
+ return;
998
+ }
999
+ mark(item);
1000
+ options.onSelect?.(index, value, item);
1001
+ });
1002
+ let reveal = null;
1003
+ if (options.reveal) reveal = revealShell(root);
1004
+ root.dataset.mkReady = "";
1005
+ return {
1006
+ select(target) {
1007
+ const item = find(target);
1008
+ if (!item) warnOnce(`nav: no item ${JSON.stringify(target)}`);
1009
+ else mark(item);
1010
+ },
1011
+ setStyle(next) {
1012
+ const resolved = toNavStyle(next);
1013
+ if (!resolved) {
1014
+ warnOnce(`nav: unknown style "${String(next)}" (use ${NAV_STYLES.join(", ")})`);
1015
+ return;
1016
+ }
1017
+ style = resolved;
1018
+ paint(accent);
1019
+ if (options.storageKey) write(options.storageKey, resolved);
1020
+ },
1021
+ setAccent(on) {
1022
+ accent = on;
1023
+ paint(accent);
1024
+ },
1025
+ get index() {
1026
+ return items2().findIndex((item) => item.getAttribute("aria-current") === "page");
1027
+ },
1028
+ get style() {
1029
+ return style;
1030
+ },
1031
+ get items() {
1032
+ return items2();
1033
+ },
1034
+ destroy() {
1035
+ reveal?.cancel();
1036
+ reveal = null;
1037
+ scope.dispose();
1038
+ delete root.dataset.mkReady;
1039
+ }
1040
+ };
1041
+ }
1042
+
1043
+ // src/components/cursor/engine.ts
1044
+ var svg = (extra = "") => `<svg aria-hidden="true" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"${extra}><path d="${ICON_PATHS.send}"/></svg>`;
1045
+ function backgroundTone(el) {
1046
+ if (!el) return "light";
1047
+ if (el.closest("img, video, picture, canvas")) return "dark";
1048
+ for (let node = el; node; node = node.parentElement) {
1049
+ const match = getComputedStyle(node).backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
1050
+ if (!match) continue;
1051
+ const alpha = match[4] === void 0 ? 1 : parseFloat(match[4]);
1052
+ if (alpha <= 0.1) continue;
1053
+ const luminance = (Number(match[1]) * 299 + Number(match[2]) * 587 + Number(match[3]) * 114) / 1e3;
1054
+ return luminance > TIMING.cursor.light ? "light" : "dark";
1055
+ }
1056
+ return "light";
1057
+ }
1058
+ function createCursor(host, options = {}) {
1059
+ const variant = options.variant ?? "chip";
1060
+ const idle = inert({ visible: false, label: "", position: { x: 0, y: 0 }, tone: "light", update() {
1061
+ } });
1062
+ if (!host) {
1063
+ warnOnce("cursor: missing element");
1064
+ return idle;
1065
+ }
1066
+ const coarse = typeof matchMedia === "function" && matchMedia("(pointer: coarse)").matches;
1067
+ if (coarse || reduced()) return idle;
1068
+ registerMotion();
1069
+ const scope = createScope();
1070
+ const t = TIMING.cursor;
1071
+ const zone = options.zone ?? (variant === "dot" ? host.parentElement ?? document.body : document.documentElement);
1072
+ scope.attr(host, "class", [host.getAttribute("class"), "mk-cursor", `mk-cursor--${variant}`].filter(Boolean).join(" "));
1073
+ scope.attr(host, "aria-hidden", "true");
1074
+ const pointer = { x: -100, y: -100, placed: false };
1075
+ const position = { x: -100, y: -100 };
1076
+ const velocity = { x: 0, y: 0 };
1077
+ let visible = false;
1078
+ let label = "";
1079
+ let tone = "light";
1080
+ const made = [];
1081
+ let chip = null;
1082
+ let chipLabel = null;
1083
+ let chipIcon = null;
1084
+ let word = null;
1085
+ const size = { width: 0, height: 0 };
1086
+ if (variant === "chip") {
1087
+ chip = document.createElement("span");
1088
+ chip.className = "mk-cursor__chip mk-glass";
1089
+ chip.innerHTML = '<span class="mk-glass__shadow" aria-hidden="true"></span><span class="mk-cursor__icon mk-icon"></span><span class="mk-cursor__label"></span>';
1090
+ chipIcon = chip.querySelector(".mk-cursor__icon");
1091
+ chipLabel = chip.querySelector(".mk-cursor__label");
1092
+ host.append(chip);
1093
+ made.push(chip);
1094
+ gsap.set(chip, { autoAlpha: 0, scale: 0.8 });
1095
+ } else {
1096
+ const arrows = options.arrows ?? "y";
1097
+ scope.attr(host, "data-mk-arrows", arrows);
1098
+ scope.attr(zone, "data-mk-cursor-zone", "");
1099
+ const dot = document.createElement("span");
1100
+ dot.className = "mk-cursor__dot";
1101
+ dot.innerHTML = arrows === "none" ? '<span class="mk-cursor__circle"></span><span class="mk-cursor__text"><span class="mk-cursor__word"></span></span>' : `<span class="mk-cursor__circle"></span><span class="mk-cursor__arrow mk-cursor__arrow--start">${svg()}</span><span class="mk-cursor__text"><span class="mk-cursor__word"></span></span><span class="mk-cursor__arrow mk-cursor__arrow--end">${svg()}</span>`;
1102
+ word = dot.querySelector(".mk-cursor__word");
1103
+ if (word) word.textContent = options.text ?? "";
1104
+ host.append(dot);
1105
+ made.push(dot);
1106
+ scope.attr(host, "data-mk-hidden", "");
1107
+ }
1108
+ const appear = motionSpring(...TIMING.springs.cursorAppear);
1109
+ const showChip = (next) => {
1110
+ if (!chip || !chipLabel || !chipIcon) return;
1111
+ const text = next?.dataset.mkCursor ?? "";
1112
+ if (text) {
1113
+ label = text;
1114
+ chipLabel.textContent = text;
1115
+ chipIcon.textContent = next?.dataset.mkCursorIcon ?? "";
1116
+ chip.classList.toggle("mk-cursor__chip--circle", next?.dataset.mkCursorShape === "circle");
1117
+ const box = chip.getBoundingClientRect();
1118
+ size.width = box.width;
1119
+ size.height = box.height;
1120
+ }
1121
+ if (Boolean(text) === visible) return;
1122
+ visible = Boolean(text);
1123
+ if (!visible) label = "";
1124
+ gsap.to(chip, {
1125
+ autoAlpha: visible ? 1 : 0,
1126
+ scale: visible ? 1 : 0.8,
1127
+ duration: appear.duration,
1128
+ ease: appear.ease,
1129
+ overwrite: "auto"
1130
+ });
1131
+ };
1132
+ let inZone = false;
1133
+ let overHide = false;
1134
+ const syncDot = () => scope.attr(host, "data-mk-hidden", inZone && !overHide ? null : "");
1135
+ const wordFor = (target) => {
1136
+ if (!word) return;
1137
+ const source = target?.closest("[data-mk-cursor]");
1138
+ word.textContent = source && zone.contains(source) ? source.dataset.mkCursor ?? "" : options.text ?? "";
1139
+ };
1140
+ const place = (event) => {
1141
+ if (event.pointerType === "touch") return;
1142
+ pointer.x = event.clientX;
1143
+ pointer.y = event.clientY;
1144
+ if (!pointer.placed) {
1145
+ pointer.placed = true;
1146
+ if (variant === "dot") {
1147
+ position.x = pointer.x;
1148
+ position.y = pointer.y;
1149
+ }
1150
+ }
1151
+ };
1152
+ scope.on(window, "pointermove", (event) => place(event), { passive: true });
1153
+ const overTarget = variant === "chip" ? document : zone;
1154
+ scope.on(overTarget, "pointerover", (event) => {
1155
+ const target = event.target;
1156
+ if (variant === "chip") {
1157
+ const source = target?.closest("[data-mk-cursor]") ?? null;
1158
+ showChip(source && zone.contains(source) ? source : null);
1159
+ return;
1160
+ }
1161
+ overHide = Boolean(target?.closest("[data-mk-cursor-hide]"));
1162
+ wordFor(target);
1163
+ syncDot();
1164
+ });
1165
+ if (variant === "dot") {
1166
+ scope.on(zone, "pointerenter", (event) => {
1167
+ place(event);
1168
+ inZone = true;
1169
+ visible = true;
1170
+ syncDot();
1171
+ });
1172
+ scope.on(zone, "pointerleave", () => {
1173
+ inZone = false;
1174
+ visible = false;
1175
+ scope.attr(host, "data-mk-pressed", null);
1176
+ syncDot();
1177
+ });
1178
+ scope.on(zone, "pointerdown", () => scope.attr(host, "data-mk-pressed", ""));
1179
+ scope.on(window, "pointerup", () => scope.attr(host, "data-mk-pressed", null));
1180
+ }
1181
+ const [stiffness, damping] = TIMING.springs.cursorFollow;
1182
+ const update = (dt = 1) => {
1183
+ if (variant === "chip") {
1184
+ const halfW = size.width / 2 + t.gutter;
1185
+ const halfH = size.height / 2 + t.gutter;
1186
+ const tx = size.width ? Math.min(Math.max(pointer.x, halfW), innerWidth - halfW) : pointer.x;
1187
+ const ty = size.height ? Math.min(Math.max(pointer.y, halfH), innerHeight - halfH) : pointer.y;
1188
+ const steps = Math.max(1, Math.ceil(dt * 4));
1189
+ const h = dt / 60 / steps;
1190
+ for (let i = 0; i < steps; i++) {
1191
+ velocity.x += (stiffness * (tx - position.x) - damping * velocity.x) * h;
1192
+ velocity.y += (stiffness * (ty - position.y) - damping * velocity.y) * h;
1193
+ position.x += velocity.x * h;
1194
+ position.y += velocity.y * h;
1195
+ }
1196
+ if (visible && typeof document.elementFromPoint === "function") {
1197
+ tone = backgroundTone(document.elementFromPoint(pointer.x, pointer.y));
1198
+ if (chip && chip.dataset.mkTone !== tone) chip.dataset.mkTone = tone;
1199
+ }
1200
+ } else {
1201
+ const step = 1 - Math.pow(1 - t.dotLerp, dt);
1202
+ position.x += (pointer.x - position.x) * step;
1203
+ position.y += (pointer.y - position.y) * step;
1204
+ }
1205
+ host.style.transform = `translate3d(${position.x.toFixed(2)}px, ${position.y.toFixed(2)}px, 0) translate(-50%, -50%)`;
1206
+ };
1207
+ scope.add(subscribe(update));
1208
+ update(0);
1209
+ host.dataset.mkReady = "";
1210
+ return {
1211
+ get visible() {
1212
+ return visible;
1213
+ },
1214
+ get label() {
1215
+ return label;
1216
+ },
1217
+ get position() {
1218
+ return { ...position };
1219
+ },
1220
+ get tone() {
1221
+ return tone;
1222
+ },
1223
+ update,
1224
+ destroy() {
1225
+ if (chip) gsap.killTweensOf(chip);
1226
+ scope.dispose();
1227
+ for (const node of made.splice(0)) node.remove();
1228
+ host.style.removeProperty("transform");
1229
+ if (host.getAttribute("style") === "") host.removeAttribute("style");
1230
+ delete host.dataset.mkReady;
1231
+ }
1232
+ };
1233
+ }
1234
+
1235
+ // src/components/island/engine.ts
1236
+ var sequence = 0;
1237
+ var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
1238
+ function createIsland(root, options = {}) {
1239
+ const shell = root?.querySelector(".mk-island__shell") ?? null;
1240
+ const panels = root ? [...root.querySelectorAll("[data-mk-island-panel]")] : [];
1241
+ if (!root || !shell || !panels.length) {
1242
+ warnOnce("island: needs .mk-island__shell and at least one [data-mk-island-panel]");
1243
+ return inert({ open() {
1244
+ }, close() {
1245
+ }, toggle() {
1246
+ }, ready() {
1247
+ }, active: null });
1248
+ }
1249
+ const t = TIMING.island;
1250
+ const { entrance = true, closeOnScroll = true, closeOnNavigate = true, focusOnOpen = true, onChange } = options;
1251
+ registerMotion();
1252
+ const scope = createScope();
1253
+ const toggles = [...root.querySelectorAll("[data-mk-island-toggle]")];
1254
+ const cta = root.querySelector(".mk-island__cta");
1255
+ const backdrops = [...root.querySelectorAll("[data-mk-island-close]")];
1256
+ let active = null;
1257
+ let trigger = null;
1258
+ let scrollStart = 0;
1259
+ let armed = false;
1260
+ let focusTimer;
1261
+ let entered = false;
1262
+ let entry = null;
1263
+ const flag = (el, name, on) => {
1264
+ if (el) scope.attr(el, name, on ? "" : null);
1265
+ };
1266
+ const sync = () => {
1267
+ for (const panel of panels) {
1268
+ const on = panel.dataset.mkIslandPanel === active;
1269
+ if (active) flag(panel, "hidden", !on);
1270
+ scope.attr(panel, "aria-hidden", String(!on));
1271
+ flag(panel, "inert", !on);
1272
+ }
1273
+ for (const button of toggles) scope.attr(button, "aria-expanded", String(button.dataset.mkIslandToggle === active));
1274
+ flag(cta, "inert", Boolean(active));
1275
+ for (const backdrop of backdrops) {
1276
+ flag(backdrop, "inert", !active);
1277
+ scope.attr(backdrop, "aria-hidden", String(!active));
1278
+ }
1279
+ flag(root, "data-mk-open", Boolean(active));
1280
+ scope.attr(root, "data-mk-active", active);
1281
+ };
1282
+ const stagger = (opening) => {
1283
+ root.querySelectorAll("[data-mk-island-item]").forEach((item, index) => {
1284
+ scope.attr(item, "style", item.getAttribute("style"));
1285
+ item.style.setProperty("--mk-island-delay", opening ? `${Number((t.delay + index * t.step).toFixed(3))}s` : "0s");
1286
+ });
1287
+ };
1288
+ const close = (returnFocus = true) => {
1289
+ if (scope.disposed || !active) return;
1290
+ clearTimeout(focusTimer);
1291
+ active = null;
1292
+ stagger(false);
1293
+ sync();
1294
+ onChange?.(null);
1295
+ if (returnFocus && trigger?.isConnected) trigger.focus({ preventScroll: true });
1296
+ };
1297
+ const open = (name = panels[0].dataset.mkIslandPanel, source) => {
1298
+ if (scope.disposed) return;
1299
+ const panel = panels.find((el) => el.dataset.mkIslandPanel === name);
1300
+ if (!panel) return;
1301
+ const wasClosed = !active;
1302
+ clearTimeout(focusTimer);
1303
+ active = name;
1304
+ trigger = source ?? toggles.find((el) => el.dataset.mkIslandToggle === name) ?? trigger;
1305
+ if (wasClosed) {
1306
+ scrollStart = window.scrollY;
1307
+ armed = false;
1308
+ stagger(true);
1309
+ }
1310
+ sync();
1311
+ onChange?.(name);
1312
+ if (focusOnOpen)
1313
+ focusTimer = setTimeout(
1314
+ () => {
1315
+ if (active !== name) return;
1316
+ const first = [...panel.querySelectorAll(FOCUSABLE)].find((el) => !el.closest("[hidden], [inert]"));
1317
+ (first ?? panel).focus({ preventScroll: true });
1318
+ },
1319
+ reduced() ? 0 : t.focus
1320
+ );
1321
+ };
1322
+ const toggle = (name = panels[0].dataset.mkIslandPanel, source) => active === name ? close(false) : open(name, source);
1323
+ for (const panel of panels) {
1324
+ if (!panel.id) scope.attr(panel, "id", `mk-island-panel-${++sequence}`);
1325
+ if (!panel.hasAttribute("tabindex")) scope.attr(panel, "tabindex", "-1");
1326
+ if (!panel.hasAttribute("role")) scope.attr(panel, "role", "region");
1327
+ }
1328
+ for (const button of toggles) {
1329
+ const panel = panels.find((el) => el.dataset.mkIslandPanel === button.dataset.mkIslandToggle);
1330
+ if (panel) scope.attr(button, "aria-controls", panel.id);
1331
+ scope.on(button, "click", () => toggle(button.dataset.mkIslandToggle, button));
1332
+ }
1333
+ for (const backdrop of backdrops) scope.on(backdrop, "click", () => close(true));
1334
+ scope.on(document, "keydown", (event) => {
1335
+ if (active && event.key === "Escape") {
1336
+ event.preventDefault();
1337
+ close(true);
1338
+ }
1339
+ });
1340
+ if (closeOnNavigate)
1341
+ scope.on(root, "click", (event) => {
1342
+ const link = event.target?.closest?.("a[href]");
1343
+ if (active && link?.closest("[data-mk-island-panel]")) close(false);
1344
+ });
1345
+ if (closeOnScroll) {
1346
+ const arm = () => {
1347
+ if (active) armed = true;
1348
+ };
1349
+ scope.on(window, "wheel", arm, { passive: true });
1350
+ scope.on(window, "touchmove", arm, { passive: true });
1351
+ scope.on(
1352
+ window,
1353
+ "scroll",
1354
+ () => {
1355
+ if (active && armed && Math.abs(window.scrollY - scrollStart) > 1) close(false);
1356
+ },
1357
+ { passive: true }
1358
+ );
1359
+ }
1360
+ scope.add(() => clearTimeout(focusTimer));
1361
+ const moving = entrance !== false && entrances();
1362
+ if (moving) gsap.set(shell, { yPercent: t.offset });
1363
+ const ready = () => {
1364
+ if (scope.disposed || entered || !moving) return;
1365
+ entered = true;
1366
+ entry = gsap.to(shell, { yPercent: 0, duration: t.entrance });
1367
+ };
1368
+ scope.add(() => {
1369
+ entry?.kill();
1370
+ if (moving) gsap.set(shell, { clearProps: "transform,translate" });
1371
+ if (shell.getAttribute("style") === "") shell.removeAttribute("style");
1372
+ });
1373
+ sync();
1374
+ if (entrance === true) ready();
1375
+ scope.attr(root, "data-mk-ready", "");
1376
+ return {
1377
+ open: (name) => open(name),
1378
+ close,
1379
+ toggle: (name) => toggle(name),
1380
+ ready,
1381
+ get active() {
1382
+ return active;
1383
+ },
1384
+ destroy: () => scope.dispose()
1385
+ };
1386
+ }
1387
+
1388
+ // src/components/pill-button/engine.ts
1389
+ var PILL_ARROW = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"/><path d="m13 6 6 6-6 6"/></svg>';
1390
+ function createPillButton(root, options = {}) {
1391
+ const control = root ? root.matches('button, a, [role="button"]') ? root : root.querySelector('button, a, [role="button"]') : null;
1392
+ if (!root || !control) return inert();
1393
+ registerMotion();
1394
+ const scope = createScope();
1395
+ const icon = control.querySelector("[data-mk-pill-icon]");
1396
+ if (!control.querySelector("[data-mk-pill-label]")) {
1397
+ const label = document.createElement("span");
1398
+ label.dataset.mkPillLabel = "";
1399
+ for (const node of [...control.childNodes]) if (node !== icon) label.append(node);
1400
+ control.prepend(label);
1401
+ scope.add(() => label.replaceWith(...label.childNodes));
1402
+ }
1403
+ if (!icon) {
1404
+ const made = document.createElement("span");
1405
+ made.dataset.mkPillIcon = "";
1406
+ made.setAttribute("aria-hidden", "true");
1407
+ made.innerHTML = options.icon ?? PILL_ARROW;
1408
+ control.append(made);
1409
+ scope.add(() => made.remove());
1410
+ }
1411
+ const fill = document.createElement("span");
1412
+ fill.dataset.mkPillFill = "";
1413
+ fill.setAttribute("aria-hidden", "true");
1414
+ control.prepend(fill);
1415
+ scope.add(() => fill.remove());
1416
+ scope.attr(control, "data-mk-pill", options.expand ? "expand" : "");
1417
+ scope.attr(root, "data-mk-ready", "");
1418
+ let tapped;
1419
+ scope.on(control, "pointerdown", (event) => {
1420
+ press(control);
1421
+ if (event.pointerType !== "touch") return;
1422
+ control.setAttribute("data-mk-active", "");
1423
+ clearTimeout(tapped);
1424
+ tapped = setTimeout(() => control.removeAttribute("data-mk-active"), 650);
1425
+ });
1426
+ scope.add(() => {
1427
+ clearTimeout(tapped);
1428
+ control.removeAttribute("data-mk-active");
1429
+ });
1430
+ return { destroy: () => scope.dispose() };
1431
+ }
1432
+
1433
+ // src/components/toolbar/engine.ts
1434
+ function createToolbar(root, options = {}) {
1435
+ const tools = root ? [...root.querySelectorAll("button")] : [];
1436
+ if (!root || !tools.length) {
1437
+ if (root) warnOnce("toolbar: needs at least one <button>");
1438
+ return inert({ setValue() {
1439
+ }, value: "" });
1440
+ }
1441
+ registerMotion();
1442
+ const scope = createScope();
1443
+ const vertical = options.orientation !== "horizontal";
1444
+ const valueOf2 = (tool) => tool.dataset.mkTool ?? String(tools.indexOf(tool));
1445
+ const labelOf = (tool) => tool.getAttribute("aria-label") ?? tool.title ?? tool.textContent?.trim() ?? "";
1446
+ scope.attr(root, "role", "toolbar");
1447
+ scope.attr(root, "aria-orientation", vertical ? "vertical" : "horizontal");
1448
+ scope.attr(root, "data-mk-orientation", vertical ? "vertical" : "horizontal");
1449
+ for (const tool of tools) {
1450
+ scope.attr(tool, "type", tool.getAttribute("type") ?? "button");
1451
+ scope.attr(tool, "aria-pressed", "false");
1452
+ scope.attr(tool, "tabindex", "-1");
1453
+ if (options.tips !== false && tool.title) {
1454
+ scope.attr(tool, "aria-label", tool.getAttribute("aria-label") ?? tool.title);
1455
+ scope.attr(tool, "title", null);
1456
+ }
1457
+ }
1458
+ const blob = document.createElement("span");
1459
+ blob.dataset.mkToolbarBlob = "";
1460
+ blob.setAttribute("aria-hidden", "true");
1461
+ root.prepend(blob);
1462
+ const tip = document.createElement("span");
1463
+ tip.dataset.mkToolbarTip = "";
1464
+ tip.setAttribute("aria-hidden", "true");
1465
+ tip.hidden = true;
1466
+ if (options.tips !== false) root.append(tip);
1467
+ scope.add(() => {
1468
+ gsap.killTweensOf([blob, tip]);
1469
+ blob.remove();
1470
+ tip.remove();
1471
+ });
1472
+ scope.attr(root, "data-mk-ready", "");
1473
+ const find = (value) => tools.find((tool) => valueOf2(tool) === value);
1474
+ let current = find(options.value) ?? tools.find((tool) => !tool.disabled) ?? tools[0];
1475
+ const box = (tool) => ({ start: vertical ? tool.offsetTop : tool.offsetLeft, size: vertical ? tool.offsetHeight : tool.offsetWidth, cross: vertical ? tool.offsetLeft : tool.offsetTop, crossSize: vertical ? tool.offsetWidth : tool.offsetHeight });
1476
+ const along = vertical ? "y" : "x";
1477
+ const across = vertical ? "x" : "y";
1478
+ const length = vertical ? "height" : "width";
1479
+ const thickness = vertical ? "width" : "height";
1480
+ let timeline = null;
1481
+ const place = () => {
1482
+ const at = box(current);
1483
+ timeline?.kill();
1484
+ gsap.set(blob, { [along]: at.start, [across]: at.cross, [length]: at.size, [thickness]: at.crossSize, scaleX: 1, scaleY: 1 });
1485
+ };
1486
+ const slide = (from, to) => {
1487
+ if (reduced() || from === to) return place();
1488
+ const a = box(from);
1489
+ const b = box(to);
1490
+ const forward = b.start > a.start;
1491
+ const reach = forward ? b.start + b.size - a.start : a.start + a.size - b.start;
1492
+ const squeeze = vertical ? { scaleX: 0.84 } : { scaleY: 0.84 };
1493
+ const relax = vertical ? { scaleX: 1 } : { scaleY: 1 };
1494
+ timeline?.kill();
1495
+ timeline = gsap.timeline();
1496
+ timeline.to(blob, { [along]: forward ? a.start : b.start, [length]: reach, ...squeeze, duration: 0.2, ease: "power2.in" }, 0);
1497
+ timeline.to(blob, { [along]: b.start, [length]: b.size, duration: 0.42, ease: EASE.swatch }, 0.16);
1498
+ timeline.to(blob, { ...relax, duration: 0.5, ease: "elastic.out(1, 0.45)" }, 0.18);
1499
+ };
1500
+ const squish = () => {
1501
+ if (reduced()) return;
1502
+ timeline?.kill();
1503
+ place();
1504
+ timeline = gsap.timeline();
1505
+ timeline.to(blob, { scaleX: 0.88, scaleY: 0.88, duration: 0.1, ease: "power2.out" }).to(blob, { scaleX: 1, scaleY: 1, duration: 0.45, ease: "elastic.out(1, 0.4)" });
1506
+ };
1507
+ const paint = () => {
1508
+ for (const tool of tools) {
1509
+ tool.setAttribute("aria-pressed", String(tool === current));
1510
+ tool.setAttribute("tabindex", tool === current ? "0" : "-1");
1511
+ }
1512
+ };
1513
+ let tipFor = null;
1514
+ let tipTimer;
1515
+ scope.add(() => clearTimeout(tipTimer));
1516
+ const showTip = (tool, linger = 0) => {
1517
+ if (options.tips === false) return;
1518
+ clearTimeout(tipTimer);
1519
+ const text = labelOf(tool);
1520
+ if (!text) return hideTip();
1521
+ const at = box(tool);
1522
+ const appearing = tip.hidden;
1523
+ const changed = tipFor !== tool;
1524
+ tipFor = tool;
1525
+ tip.textContent = text;
1526
+ tip.hidden = false;
1527
+ const tipSize = vertical ? tip.offsetHeight : tip.offsetWidth;
1528
+ const spot = at.start + at.size / 2 - tipSize / 2;
1529
+ if (appearing || reduced()) {
1530
+ gsap.killTweensOf(tip);
1531
+ gsap.set(tip, { [along]: spot });
1532
+ if (!reduced()) gsap.fromTo(tip, { opacity: 0, [across]: -8, filter: "blur(4px)", scale: 0.9 }, { opacity: 1, [across]: 0, filter: "blur(0px)", scale: 1, duration: 0.28, ease: EASE.pop });
1533
+ else gsap.set(tip, { opacity: 1, [across]: 0, scale: 1, filter: "none" });
1534
+ } else if (changed) {
1535
+ gsap.to(tip, { [along]: spot, opacity: 1, [across]: 0, scale: 1, filter: "blur(0px)", duration: 0.32, ease: EASE.swatch, overwrite: "auto" });
1536
+ }
1537
+ if (linger) tipTimer = setTimeout(hideTip, linger);
1538
+ };
1539
+ const hideTip = () => {
1540
+ clearTimeout(tipTimer);
1541
+ tipFor = null;
1542
+ if (tip.hidden) return;
1543
+ if (reduced()) {
1544
+ tip.hidden = true;
1545
+ return;
1546
+ }
1547
+ gsap.to(tip, { opacity: 0, [across]: -6, filter: "blur(4px)", scale: 0.92, duration: 0.18, ease: "power2.in", overwrite: "auto", onComplete: () => {
1548
+ tip.hidden = true;
1549
+ } });
1550
+ };
1551
+ const choose = (tool, notify) => {
1552
+ if (tool.disabled) return;
1553
+ const previous = current;
1554
+ current = tool;
1555
+ paint();
1556
+ if (previous === current) squish();
1557
+ else slide(previous, current);
1558
+ if (previous === current || !notify) return;
1559
+ const value = valueOf2(current);
1560
+ options.onChange?.(value);
1561
+ root.dispatchEvent(new CustomEvent("mk:change", { bubbles: true, detail: { value } }));
1562
+ };
1563
+ scope.on(root, "click", (event) => {
1564
+ const tool = event.target.closest("button");
1565
+ if (!tool || !tools.includes(tool)) return;
1566
+ press(tool);
1567
+ choose(tool, true);
1568
+ showTip(tool, 1200);
1569
+ });
1570
+ scope.on(root, "pointerover", (event) => {
1571
+ const tool = event.target.closest("button");
1572
+ if (tool && tools.includes(tool) && event.pointerType !== "touch") showTip(tool);
1573
+ });
1574
+ scope.on(root, "pointerleave", () => {
1575
+ tipTimer = setTimeout(hideTip, 120);
1576
+ });
1577
+ scope.on(root, "focusin", (event) => {
1578
+ const tool = event.target;
1579
+ if (tools.includes(tool) && tool.matches(":focus-visible")) showTip(tool);
1580
+ });
1581
+ scope.on(root, "focusout", (event) => {
1582
+ if (!root.contains(event.relatedTarget)) hideTip();
1583
+ });
1584
+ scope.on(root, "keydown", (event) => {
1585
+ const key = event.key;
1586
+ const enabled = tools.filter((tool) => !tool.disabled);
1587
+ const at = enabled.indexOf(document.activeElement);
1588
+ if (at < 0) return;
1589
+ const next = key === "ArrowDown" || key === "ArrowRight" ? enabled[(at + 1) % enabled.length] : key === "ArrowUp" || key === "ArrowLeft" ? enabled[(at - 1 + enabled.length) % enabled.length] : key === "Home" ? enabled[0] : key === "End" ? enabled[enabled.length - 1] : null;
1590
+ if (!next) return;
1591
+ event.preventDefault();
1592
+ choose(next, true);
1593
+ next.focus();
1594
+ showTip(next, 1200);
1595
+ });
1596
+ if (typeof ResizeObserver === "function") scope.observe(new ResizeObserver(() => place())).observe(root);
1597
+ paint();
1598
+ place();
1599
+ return {
1600
+ setValue(value, notify = false) {
1601
+ const tool = find(value);
1602
+ if (tool) choose(tool, notify);
1603
+ },
1604
+ get value() {
1605
+ return valueOf2(current);
1606
+ },
1607
+ destroy: () => scope.dispose()
1608
+ };
1609
+ }
1610
+
1611
+ // src/components/bidirectional-slider/engine.ts
1612
+ var finite = (value) => typeof value === "number" && Number.isFinite(value);
1613
+ var ICONS = {
1614
+ signs: ['<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 12h12"/></svg>', '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 12h12M12 6v12"/></svg>'],
1615
+ volume: [
1616
+ '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 9.5v5h3.5L12 18.5v-13L7.5 9.5z"/><path d="M15.5 9.5a3.5 3.5 0 0 1 0 5"/></svg>',
1617
+ '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 9.5v5h3.5L12 18.5v-13L7.5 9.5z"/><path d="M15.5 9.5a3.5 3.5 0 0 1 0 5"/><path d="M18 7a7 7 0 0 1 0 10"/></svg>'
1618
+ ]
1619
+ };
1620
+ function createBidirectionalSlider(root, options = {}) {
1621
+ if (!root) return inert({ setValue() {
1622
+ }, reset() {
1623
+ }, value: 0 });
1624
+ registerMotion();
1625
+ const scope = createScope();
1626
+ const min = finite(options.min) ? options.min : -100;
1627
+ const max = finite(options.max) && options.max > min ? options.max : Math.max(min + 1, 100);
1628
+ const origin = Math.min(max, Math.max(min, finite(options.origin) ? options.origin : 0));
1629
+ const step = finite(options.step) && options.step > 0 ? options.step : 1;
1630
+ const decimals = (String(step).split(".")[1] ?? "").length;
1631
+ const snap = (n) => Number(Math.min(max, Math.max(min, Math.round((n - min) / step) * step + min)).toFixed(decimals));
1632
+ const format = options.formatValue ?? ((n) => {
1633
+ const delta = Number((n - origin).toFixed(decimals));
1634
+ const text = new Intl.NumberFormat(void 0, { maximumFractionDigits: decimals }).format(Math.abs(delta));
1635
+ return delta > 0 ? `+${text}` : delta < 0 ? `\u2212${text}` : text;
1636
+ });
1637
+ const made = (tag, parent, data) => {
1638
+ const element = document.createElement(tag);
1639
+ element.setAttribute(`data-mk-bislider-${data}`, "");
1640
+ parent.append(element);
1641
+ scope.add(() => element.remove());
1642
+ return element;
1643
+ };
1644
+ const icons = options.icons === false ? null : ICONS[options.icons ?? "signs"];
1645
+ const less = icons ? made("button", root, "less") : null;
1646
+ const track = made("span", root, "track");
1647
+ const more = icons ? made("button", root, "more") : null;
1648
+ if (less && more && icons) {
1649
+ less.type = more.type = "button";
1650
+ less.tabIndex = more.tabIndex = -1;
1651
+ less.innerHTML = icons[0];
1652
+ more.innerHTML = icons[1];
1653
+ less.setAttribute("aria-label", "Menos");
1654
+ more.setAttribute("aria-label", "M\xE1s");
1655
+ root.prepend(less);
1656
+ }
1657
+ const clip = made("span", track, "clip");
1658
+ clip.setAttribute("aria-hidden", "true");
1659
+ const fill = made("span", clip, "fill");
1660
+ const notch = made("span", clip, "notch");
1661
+ const thumb = made("span", track, "thumb");
1662
+ const bubble = made("span", track, "bubble");
1663
+ for (const part2 of [fill, notch, thumb, bubble]) part2.setAttribute("aria-hidden", "true");
1664
+ let input = root.querySelector('input[type="range"]');
1665
+ if (input) {
1666
+ const kept = input;
1667
+ const place = { parent: kept.parentNode, next: kept.nextSibling };
1668
+ const saved = ["min", "max", "step", "value", "data-mk-bislider-input"].map((name) => [name, kept.getAttribute(name)]);
1669
+ const value2 = kept.value;
1670
+ scope.add(() => {
1671
+ for (const [name, was] of saved) {
1672
+ if (was === null) kept.removeAttribute(name);
1673
+ else kept.setAttribute(name, was);
1674
+ }
1675
+ kept.value = value2;
1676
+ place.parent?.insertBefore(kept, place.next);
1677
+ });
1678
+ } else {
1679
+ input = document.createElement("input");
1680
+ input.type = "range";
1681
+ scope.add(() => input.remove());
1682
+ }
1683
+ const range = input;
1684
+ track.append(range);
1685
+ range.dataset.mkBisliderInput = "";
1686
+ range.min = String(min);
1687
+ range.max = String(max);
1688
+ range.step = String(step);
1689
+ if (options.name) scope.attr(range, "name", options.name);
1690
+ if (options.label && !range.getAttribute("aria-label")) scope.attr(range, "aria-label", options.label);
1691
+ if (options.disabled) scope.attr(range, "disabled", "");
1692
+ const start = snap(finite(options.value) ? options.value : range.getAttribute("value") !== null ? Number(range.getAttribute("value")) : origin);
1693
+ range.value = String(start);
1694
+ range.defaultValue = String(start);
1695
+ scope.attr(root, "data-mk-ready", "");
1696
+ let value = start;
1697
+ let committed = start;
1698
+ const shown = { v: start };
1699
+ const pct = (n) => (n - min) / (max - min) * 100;
1700
+ notch.style.left = `${pct(origin)}%`;
1701
+ const paint = () => {
1702
+ const at = pct(shown.v);
1703
+ const from = pct(origin);
1704
+ fill.style.left = `${Math.min(at, from)}%`;
1705
+ fill.style.width = `${Math.abs(at - from)}%`;
1706
+ thumb.style.left = `${at}%`;
1707
+ bubble.style.left = `${at}%`;
1708
+ root.setAttribute("data-mk-side", shown.v > origin ? "more" : shown.v < origin ? "less" : "origin");
1709
+ };
1710
+ scope.add(() => root.removeAttribute("data-mk-side"));
1711
+ const describe = () => {
1712
+ const text = format(value);
1713
+ bubble.textContent = text;
1714
+ range.setAttribute("aria-valuetext", text);
1715
+ };
1716
+ scope.add(() => range.removeAttribute("aria-valuetext"));
1717
+ const effects = /* @__PURE__ */ new Set();
1718
+ const keep = (animation) => {
1719
+ if (!animation) return;
1720
+ effects.add(animation);
1721
+ for (const effect of effects) if (effect.totalProgress() === 1) effects.delete(effect);
1722
+ };
1723
+ scope.add(() => {
1724
+ gsap.killTweensOf(shown);
1725
+ for (const effect of effects) effect.kill();
1726
+ effects.clear();
1727
+ });
1728
+ const bump = (element, scale = 1.25) => {
1729
+ if (!element || reduced()) return;
1730
+ keep(gsap.fromTo(element, { scale }, { scale: 1, duration: 0.5, ease: "elastic.out(1, 0.4)", overwrite: "auto" }));
1731
+ };
1732
+ let held = false;
1733
+ const set = (next, glide, notify) => {
1734
+ const before = value;
1735
+ value = snap(next);
1736
+ range.value = String(value);
1737
+ describe();
1738
+ gsap.killTweensOf(shown);
1739
+ if (glide && !reduced()) keep(gsap.to(shown, { v: value, duration: 0.45, ease: EASE.swatch, onUpdate: paint }));
1740
+ else {
1741
+ shown.v = value;
1742
+ paint();
1743
+ }
1744
+ if ((before - origin) * (value - origin) < 0 || before !== origin && value === origin) bump(notch, 1.8);
1745
+ if (value !== before && value === max) bump(more);
1746
+ if (value !== before && value === min) bump(less);
1747
+ if (notify && value !== before) {
1748
+ options.onInput?.(value);
1749
+ root.dispatchEvent(new CustomEvent("mk:input", { bubbles: true, detail: { value } }));
1750
+ }
1751
+ if (notify === "change") commit();
1752
+ };
1753
+ const commit = () => {
1754
+ if (value === committed) return;
1755
+ committed = value;
1756
+ options.onChange?.(value);
1757
+ root.dispatchEvent(new CustomEvent("mk:change", { bubbles: true, detail: { value } }));
1758
+ };
1759
+ scope.on(range, "input", () => {
1760
+ const next = Number(range.value);
1761
+ const before = value;
1762
+ set(next, !held, false);
1763
+ if (value !== before) {
1764
+ options.onInput?.(value);
1765
+ root.dispatchEvent(new CustomEvent("mk:input", { bubbles: true, detail: { value } }));
1766
+ }
1767
+ });
1768
+ scope.on(range, "change", commit);
1769
+ scope.on(range, "dblclick", () => set(origin, true, "change"));
1770
+ let pointer = null;
1771
+ scope.on(range, "pointerdown", (event) => {
1772
+ const down = event;
1773
+ if (range.disabled || down.button !== 0) return;
1774
+ held = true;
1775
+ pointer = down.pointerId;
1776
+ root.setAttribute("data-mk-held", "");
1777
+ });
1778
+ scope.on(window, "pointermove", (event) => {
1779
+ const move = event;
1780
+ if (!held || move.pointerId !== pointer || reduced()) return;
1781
+ const box = track.getBoundingClientRect();
1782
+ const over = move.clientX > box.right ? move.clientX - box.right : move.clientX < box.left ? move.clientX - box.left : 0;
1783
+ const stretch = Math.min(1, Math.abs(over) / 80) * 0.04;
1784
+ gsap.set(track, { scaleX: 1 + stretch, transformOrigin: over > 0 ? "0% 50%" : "100% 50%" });
1785
+ });
1786
+ const release = (event) => {
1787
+ if (!held || event.pointerId !== pointer) return;
1788
+ held = false;
1789
+ pointer = null;
1790
+ root.removeAttribute("data-mk-held");
1791
+ if (!reduced()) keep(gsap.to(track, { scaleX: 1, duration: 0.6, ease: "elastic.out(1, 0.35)", clearProps: "transform,transformOrigin" }));
1792
+ };
1793
+ scope.on(window, "pointerup", release);
1794
+ scope.on(window, "pointercancel", release);
1795
+ scope.add(() => {
1796
+ root.removeAttribute("data-mk-held");
1797
+ gsap.set(track, { clearProps: "transform,transformOrigin" });
1798
+ });
1799
+ let repeat;
1800
+ const stop = () => {
1801
+ clearTimeout(repeat);
1802
+ if (repeat !== void 0) commit();
1803
+ repeat = void 0;
1804
+ };
1805
+ scope.add(() => clearTimeout(repeat));
1806
+ for (const [button, direction] of [[less, -1], [more, 1]]) {
1807
+ if (!button) continue;
1808
+ scope.on(button, "pointerdown", (event) => {
1809
+ if (range.disabled || event.button !== 0) return;
1810
+ event.preventDefault();
1811
+ press(button);
1812
+ const tick = (delay) => {
1813
+ set(value + step * direction, true, "input");
1814
+ repeat = setTimeout(() => tick(Math.max(50, delay * 0.8)), delay);
1815
+ };
1816
+ tick(400);
1817
+ });
1818
+ scope.on(button, "pointerup", stop);
1819
+ scope.on(button, "pointerleave", stop);
1820
+ scope.on(button, "pointercancel", stop);
1821
+ }
1822
+ const form = range.form;
1823
+ if (form) scope.on(form, "reset", () => setTimeout(() => set(Number(range.defaultValue), true, false)));
1824
+ describe();
1825
+ paint();
1826
+ return {
1827
+ setValue: (next, notify = false) => set(next, true, notify ? "change" : false),
1828
+ reset: (notify = false) => set(origin, true, notify ? "change" : false),
1829
+ get value() {
1830
+ return value;
1831
+ },
1832
+ destroy: () => scope.dispose()
1833
+ };
1834
+ }
1835
+
1836
+ // src/components/voice-dots/engine.ts
1837
+ var LABELS = { idle: "Asistente en espera", listening: "Escuchando\u2026", thinking: "Pensando\u2026", speaking: "Hablando\u2026" };
1838
+ var STATES = ["idle", "listening", "thinking", "speaking"];
1839
+ var mediaSources = /* @__PURE__ */ new WeakMap();
1840
+ var audio = null;
1841
+ function createVoiceDots(root, options = {}) {
1842
+ if (!root) return inert({ setState() {
1843
+ }, state: "idle", setLevel() {
1844
+ }, listen: async () => () => {
1845
+ }, stop() {
1846
+ } });
1847
+ registerMotion();
1848
+ const scope = createScope();
1849
+ const count = Math.max(3, Math.min(6, Math.round(options.count ?? 4)));
1850
+ const size = Math.max(4, options.size ?? 12);
1851
+ const labels = { ...LABELS, ...options.labels };
1852
+ let state = STATES.includes(options.state) ? options.state : "idle";
1853
+ const row = document.createElement("span");
1854
+ row.dataset.mkVoiceRow = "";
1855
+ row.setAttribute("aria-hidden", "true");
1856
+ const dots = Array.from({ length: count }, (_, i) => {
1857
+ const dot = document.createElement("span");
1858
+ dot.dataset.mkVoiceDot = String(i + 1);
1859
+ row.append(dot);
1860
+ return dot;
1861
+ });
1862
+ const status = document.createElement("span");
1863
+ status.className = "mk-sr-only";
1864
+ status.setAttribute("role", "status");
1865
+ root.append(row, status);
1866
+ scope.add(() => {
1867
+ row.remove();
1868
+ status.remove();
1869
+ });
1870
+ scope.attr(root, "data-mk-ready", "");
1871
+ scope.attr(root, "data-mk-state", state);
1872
+ if (options.colorful) scope.attr(root, "data-mk-colorful", "");
1873
+ root.style.setProperty("--mk-voice-size", `${size}px`);
1874
+ scope.add(() => {
1875
+ root.style.removeProperty("--mk-voice-size");
1876
+ if (!root.getAttribute("style")) root.removeAttribute("style");
1877
+ });
1878
+ const drawn = dots.map(() => ({ h: size, x: 0, y: 0, vh: 0 }));
1879
+ const levels = dots.map(() => 0);
1880
+ let time = 0;
1881
+ const gap = size * 0.9;
1882
+ const home = (i) => (i - (count - 1) / 2) * (size + gap);
1883
+ const draw = () => {
1884
+ dots.forEach((dot, i) => {
1885
+ const d = drawn[i];
1886
+ dot.style.height = `${d.h.toFixed(2)}px`;
1887
+ dot.style.transform = `translate(${d.x.toFixed(2)}px, ${d.y.toFixed(2)}px)`;
1888
+ });
1889
+ };
1890
+ const still = () => {
1891
+ dots.forEach((_, i) => Object.assign(drawn[i], { h: size, x: 0, y: 0, vh: 0 }));
1892
+ draw();
1893
+ };
1894
+ let analyser = null;
1895
+ let bins = null;
1896
+ let release = null;
1897
+ let listening = 0;
1898
+ const sample = () => {
1899
+ if (!analyser || !bins) return;
1900
+ analyser.getByteFrequencyData(bins);
1901
+ const hz = (audio?.sampleRate ?? 48e3) / 2 / bins.length;
1902
+ const low = Math.max(1, Math.floor(120 / hz));
1903
+ const high = Math.min(bins.length - 1, Math.ceil(3e3 / hz));
1904
+ const span = (high - low) / count;
1905
+ for (let i = 0; i < count; i++) {
1906
+ let sum = 0;
1907
+ const from = Math.floor(low + span * i);
1908
+ const to = Math.max(from + 1, Math.floor(low + span * (i + 1)));
1909
+ for (let k = from; k < to; k++) sum += bins[k];
1910
+ levels[i] = Math.min(1, sum / (to - from) / 255 * (1.4 + i * 0.35));
1911
+ }
1912
+ };
1913
+ const tick = (dt) => {
1914
+ if (reduced()) return;
1915
+ time += dt / 60;
1916
+ sample();
1917
+ const k = 1 - Math.exp(-dt * 0.35);
1918
+ dots.forEach((_, i) => {
1919
+ const d = drawn[i];
1920
+ let h = size;
1921
+ let x = 0;
1922
+ let y = 0;
1923
+ if (state === "idle") {
1924
+ h = size * (1 + 0.18 * Math.max(0, Math.sin(time * 3.2 - i * 0.7)));
1925
+ y = -2 * Math.max(0, Math.sin(time * 3.2 - i * 0.7));
1926
+ } else if (state === "listening") {
1927
+ h = size + levels[i] * size * 3.2;
1928
+ } else if (state === "thinking") {
1929
+ const angle = time * 4 + i / count * Math.PI * 2;
1930
+ const ring = size * 1.2;
1931
+ x = Math.cos(angle) * ring - home(i);
1932
+ y = Math.sin(angle) * ring;
1933
+ } else {
1934
+ const wave = 0.5 + 0.5 * Math.sin(time * 9 - i * 0.9);
1935
+ const voice = analyser ? levels[i] : 0.55;
1936
+ h = size + wave * voice * size * 2.4;
1937
+ }
1938
+ const stiffness = h > d.h ? 0.5 : 0.18;
1939
+ d.vh = (d.vh + (h - d.h) * stiffness) * 0.6;
1940
+ d.h = Math.max(size * 0.8, d.h + d.vh * dt);
1941
+ d.x += (x - d.x) * k;
1942
+ d.y += (y - d.y) * k;
1943
+ });
1944
+ draw();
1945
+ };
1946
+ const stopLoop = whileVisible(root, tick);
1947
+ scope.add(stopLoop);
1948
+ const stop = () => {
1949
+ listening++;
1950
+ release?.();
1951
+ release = null;
1952
+ analyser = null;
1953
+ bins = null;
1954
+ levels.fill(0);
1955
+ };
1956
+ scope.add(stop);
1957
+ const setState = (next) => {
1958
+ if (!STATES.includes(next)) return;
1959
+ state = next;
1960
+ root.setAttribute("data-mk-state", next);
1961
+ status.textContent = next === "idle" ? "" : labels[next];
1962
+ if (reduced()) still();
1963
+ root.dispatchEvent(new CustomEvent("mk:state", { bubbles: true, detail: { state: next } }));
1964
+ };
1965
+ draw();
1966
+ if (state !== "idle") status.textContent = labels[state];
1967
+ return {
1968
+ setState,
1969
+ get state() {
1970
+ return state;
1971
+ },
1972
+ setLevel(level) {
1973
+ const list = Array.isArray(level) ? level : dots.map(() => level);
1974
+ dots.forEach((_, i) => {
1975
+ const value = Number(list[i % Math.max(1, list.length)]);
1976
+ levels[i] = Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : 0;
1977
+ });
1978
+ },
1979
+ async listen(source) {
1980
+ stop();
1981
+ const request = listening;
1982
+ const Context = globalThis.AudioContext ?? globalThis.webkitAudioContext;
1983
+ if (!Context || scope.disposed) return () => {
1984
+ };
1985
+ audio ??= new Context();
1986
+ if (audio.state === "suspended") await audio.resume().catch(() => {
1987
+ });
1988
+ if (scope.disposed || request !== listening) return () => {
1989
+ };
1990
+ const node = audio.createAnalyser();
1991
+ node.fftSize = 256;
1992
+ node.smoothingTimeConstant = 0.55;
1993
+ let input;
1994
+ if (source instanceof HTMLMediaElement) {
1995
+ const existing = mediaSources.get(source) ?? audio.createMediaElementSource(source);
1996
+ mediaSources.set(source, existing);
1997
+ existing.disconnect();
1998
+ input = existing;
1999
+ node.connect(audio.destination);
2000
+ } else input = audio.createMediaStreamSource(source);
2001
+ input.connect(node);
2002
+ analyser = node;
2003
+ bins = new Uint8Array(new ArrayBuffer(node.frequencyBinCount));
2004
+ const mine = () => {
2005
+ try {
2006
+ input.disconnect(node);
2007
+ node.disconnect();
2008
+ } catch {
2009
+ }
2010
+ if (source instanceof HTMLMediaElement && audio) input.connect(audio.destination);
2011
+ };
2012
+ release = mine;
2013
+ return () => {
2014
+ if (release === mine) stop();
2015
+ };
2016
+ },
2017
+ stop,
2018
+ destroy: () => scope.dispose()
2019
+ };
2020
+ }
2021
+
2022
+ // src/components/chart/parts.ts
2023
+ var SVG_NS = "http://www.w3.org/2000/svg";
2024
+ var SLOTS = 8;
2025
+ var svg2 = (tag, attrs = {}) => {
2026
+ const element = document.createElementNS(SVG_NS, tag);
2027
+ for (const [name, value] of Object.entries(attrs)) element.setAttribute(name, String(value));
2028
+ return element;
2029
+ };
2030
+ var finite2 = (value) => typeof value === "number" && Number.isFinite(value);
2031
+ var round = (n) => Math.round(n * 100) / 100;
2032
+ var slotColor = (i, total = SLOTS) => total === 1 ? "var(--mk-chart-mono)" : i < SLOTS ? `var(--mk-chart-${i + 1})` : "var(--mk-chart-other)";
2033
+ var formatNumber = (value) => new Intl.NumberFormat(void 0, { maximumFractionDigits: 2 }).format(value);
2034
+ var polar = (cx, cy, r, angle) => [cx + r * Math.sin(angle), cy - r * Math.cos(angle)];
2035
+ function arcPath(cx, cy, outer, inner, a0, a1) {
2036
+ const sweep = Math.min(a1 - a0, Math.PI * 2 - 1e-4);
2037
+ const end = a0 + sweep;
2038
+ const large = sweep > Math.PI ? 1 : 0;
2039
+ const [x0, y0] = polar(cx, cy, outer, a0);
2040
+ const [x1, y1] = polar(cx, cy, outer, end);
2041
+ if (inner <= 0) return `M${round(cx)},${round(cy)}L${round(x0)},${round(y0)}A${round(outer)},${round(outer)} 0 ${large} 1 ${round(x1)},${round(y1)}Z`;
2042
+ const [x2, y2] = polar(cx, cy, inner, end);
2043
+ const [x3, y3] = polar(cx, cy, inner, a0);
2044
+ return `M${round(x0)},${round(y0)}A${round(outer)},${round(outer)} 0 ${large} 1 ${round(x1)},${round(y1)}L${round(x2)},${round(y2)}A${round(inner)},${round(inner)} 0 ${large} 0 ${round(x3)},${round(y3)}Z`;
2045
+ }
2046
+ function chartFrame(root, scope, kind) {
2047
+ if (!root.classList.contains("mk-chart")) {
2048
+ root.classList.add("mk-chart");
2049
+ scope.add(() => {
2050
+ root.classList.remove("mk-chart");
2051
+ if (!root.classList.length) root.removeAttribute("class");
2052
+ });
2053
+ }
2054
+ scope.attr(root, "data-mk-chart-type", kind);
2055
+ const made = (tag, parent = root) => {
2056
+ const element = document.createElement(tag);
2057
+ parent.append(element);
2058
+ scope.add(() => element.remove());
2059
+ return element;
2060
+ };
2061
+ const plot = made("div");
2062
+ plot.dataset.mkChartPlot = "";
2063
+ const canvas = svg2("svg");
2064
+ canvas.dataset.mkChartSvg = "";
2065
+ canvas.setAttribute("role", "img");
2066
+ canvas.setAttribute("tabindex", "0");
2067
+ canvas.setAttribute("focusable", "true");
2068
+ plot.append(canvas);
2069
+ const tooltip = made("div", plot);
2070
+ tooltip.dataset.mkChartTooltip = "";
2071
+ tooltip.setAttribute("aria-hidden", "true");
2072
+ tooltip.hidden = true;
2073
+ const empty = made("p");
2074
+ empty.dataset.mkChartEmpty = "";
2075
+ empty.hidden = true;
2076
+ const legend = made("ul");
2077
+ legend.dataset.mkChartLegend = "";
2078
+ const table = made("table");
2079
+ table.dataset.mkChartTable = "";
2080
+ table.className = "mk-sr-only";
2081
+ const live = made("span");
2082
+ live.className = "mk-sr-only";
2083
+ live.setAttribute("role", "status");
2084
+ scope.attr(root, "data-mk-ready", "");
2085
+ const effects = /* @__PURE__ */ new Set();
2086
+ const keep = (animation) => {
2087
+ if (!animation) return;
2088
+ effects.add(animation);
2089
+ for (const effect of effects) if (effect.totalProgress() === 1) effects.delete(effect);
2090
+ };
2091
+ scope.add(() => {
2092
+ for (const effect of effects) effect.kill();
2093
+ effects.clear();
2094
+ gsap.killTweensOf(tooltip);
2095
+ });
2096
+ const tip = (at) => {
2097
+ if (!at) {
2098
+ if (!tooltip.hidden && !reduced()) {
2099
+ gsap.killTweensOf(tooltip, "opacity,scale");
2100
+ keep(gsap.to(tooltip, { opacity: 0, scale: 0.94, duration: 0.15, ease: "power2.in", onComplete: () => {
2101
+ tooltip.hidden = true;
2102
+ } }));
2103
+ } else tooltip.hidden = true;
2104
+ return;
2105
+ }
2106
+ tooltip.replaceChildren();
2107
+ const title = document.createElement("strong");
2108
+ title.textContent = at.title;
2109
+ tooltip.append(title);
2110
+ for (const each of at.rows) {
2111
+ const row = document.createElement("span");
2112
+ row.dataset.mkChartRow = "";
2113
+ const swatch = document.createElement("i");
2114
+ if (!each.color || /--mk-(text|chart-mono|muted)/.test(each.color)) swatch.dataset.mkMono = "";
2115
+ else swatch.style.background = each.color;
2116
+ const name = document.createElement("span");
2117
+ name.textContent = each.name;
2118
+ const amount = document.createElement("b");
2119
+ amount.textContent = each.value;
2120
+ row.append(swatch, name, amount);
2121
+ tooltip.append(row);
2122
+ }
2123
+ const appearing = tooltip.hidden;
2124
+ tooltip.hidden = false;
2125
+ const width = plot.clientWidth || 300;
2126
+ const height = plot.clientHeight || 240;
2127
+ const tipW = tooltip.offsetWidth || 160;
2128
+ const tipH = tooltip.offsetHeight || 60;
2129
+ const left = round(Math.max(0, Math.min(width - tipW, at.x > width / 2 ? at.x - 16 - tipW : at.x + 16)));
2130
+ const top = round(Math.max(0, Math.min(height - tipH, at.y - tipH - 12)));
2131
+ if (appearing || reduced()) {
2132
+ gsap.killTweensOf(tooltip);
2133
+ gsap.set(tooltip, { x: left, y: top });
2134
+ if (!reduced()) keep(gsap.fromTo(tooltip, { opacity: 0, scale: 0.9 }, { opacity: 1, scale: 1, duration: 0.25, ease: EASE.pop }));
2135
+ else gsap.set(tooltip, { opacity: 1, scale: 1 });
2136
+ } else {
2137
+ gsap.killTweensOf(tooltip, "opacity,scale");
2138
+ gsap.set(tooltip, { opacity: 1, scale: 1 });
2139
+ keep(gsap.to(tooltip, { x: left, y: top, duration: 0.25, ease: "power3.out", overwrite: "auto" }));
2140
+ }
2141
+ };
2142
+ const fillLegend = (items2, show2, type = "bar") => {
2143
+ legend.replaceChildren();
2144
+ legend.hidden = !show2 || !items2.length;
2145
+ if (legend.hidden) return [];
2146
+ return items2.map((item) => {
2147
+ const li = document.createElement("li");
2148
+ const swatch = document.createElement("span");
2149
+ swatch.dataset.mkChartSwatch = "";
2150
+ swatch.dataset.mkType = type;
2151
+ swatch.style.background = item.color;
2152
+ swatch.style.color = item.color;
2153
+ swatch.setAttribute("aria-hidden", "true");
2154
+ const name = document.createElement("span");
2155
+ name.textContent = item.name;
2156
+ li.append(swatch, name);
2157
+ legend.append(li);
2158
+ return li;
2159
+ });
2160
+ };
2161
+ const fillTable = (caption, head, rows) => {
2162
+ table.replaceChildren();
2163
+ const cap = document.createElement("caption");
2164
+ cap.textContent = caption;
2165
+ const thead = document.createElement("thead");
2166
+ const headRow = document.createElement("tr");
2167
+ for (const text of head) {
2168
+ const th = document.createElement("th");
2169
+ th.scope = "col";
2170
+ th.textContent = text;
2171
+ headRow.append(th);
2172
+ }
2173
+ thead.append(headRow);
2174
+ const body = document.createElement("tbody");
2175
+ for (const cells of rows) {
2176
+ const row = document.createElement("tr");
2177
+ cells.forEach((text, i) => {
2178
+ const cell = document.createElement(i ? "td" : "th");
2179
+ if (!i) cell.scope = "row";
2180
+ cell.textContent = text;
2181
+ row.append(cell);
2182
+ });
2183
+ body.append(row);
2184
+ }
2185
+ table.append(cap, thead, body);
2186
+ };
2187
+ return { plot, canvas, tooltip, empty, legend, table, live, tip, fillLegend, fillTable, keep };
2188
+ }
2189
+ function onWidth(root, scope, draw) {
2190
+ if (typeof ResizeObserver !== "function") return;
2191
+ let width = 0;
2192
+ let frame = 0;
2193
+ const observer = scope.observe(
2194
+ new ResizeObserver(() => {
2195
+ const next = Math.round(root.clientWidth);
2196
+ if (!next || next === width) return;
2197
+ const first = !width;
2198
+ width = next;
2199
+ if (first) return;
2200
+ cancelAnimationFrame(frame);
2201
+ frame = requestAnimationFrame(draw);
2202
+ })
2203
+ );
2204
+ observer.observe(root);
2205
+ scope.add(() => cancelAnimationFrame(frame));
2206
+ }
2207
+
2208
+ // src/components/pie-chart/engine.ts
2209
+ var PULL = 7;
2210
+ function createPieChart(root, initial = {}) {
2211
+ if (!root) return inert({ update() {
2212
+ }, select() {
2213
+ }, selected: null });
2214
+ registerMotion();
2215
+ const scope = createScope();
2216
+ const frame = chartFrame(root, scope, "pie");
2217
+ const { plot, canvas, keep } = frame;
2218
+ let options = { ...initial };
2219
+ let selected = null;
2220
+ const center = document.createElement("div");
2221
+ center.dataset.mkPieCenter = "";
2222
+ center.setAttribute("aria-hidden", "true");
2223
+ const centerValue = document.createElement("strong");
2224
+ const centerLabel = document.createElement("span");
2225
+ center.append(centerValue, centerLabel);
2226
+ plot.append(center);
2227
+ scope.add(() => center.remove());
2228
+ const format = (value) => options.formatValue ? options.formatValue(value) : formatNumber(value);
2229
+ const slices = () => (options.data ?? []).filter((slice) => finite2(slice.value) && slice.value > 0).map((slice, i, all) => ({ label: String(slice.label), value: slice.value, color: slice.color ?? slotColor(i, Math.max(2, all.length)) }));
2230
+ let model = slices();
2231
+ const total = () => model.reduce((sum, slice) => sum + slice.value, 0);
2232
+ const share = (value) => new Intl.NumberFormat(void 0, { style: "percent", maximumFractionDigits: 1 }).format(value / (total() || 1));
2233
+ const donut = () => Math.min(0.9, Math.max(0, finite2(options.donut) ? options.donut : 0.62));
2234
+ const anglesOf = (values) => {
2235
+ const sum = values.reduce((a, b) => a + b, 0) || 1;
2236
+ const pad = values.filter((v) => v > 0).length > 1 ? (finite2(options.gap) ? options.gap : 1.5) * Math.PI / 180 : 0;
2237
+ let at = 0;
2238
+ return values.map((value) => {
2239
+ const sweep = value / sum * Math.PI * 2;
2240
+ const a0 = at + Math.min(pad / 2, sweep / 4);
2241
+ const a1 = at + sweep - Math.min(pad / 2, sweep / 4);
2242
+ at += sweep;
2243
+ return [a0, Math.max(a0, a1)];
2244
+ });
2245
+ };
2246
+ let paths = [];
2247
+ let shown = [];
2248
+ let geometry = { size: 240, cx: 120, cy: 120, outer: 112, inner: 70 };
2249
+ let glide = null;
2250
+ scope.add(() => glide?.kill());
2251
+ const measure = () => {
2252
+ const width = Math.round(plot.clientWidth || root.clientWidth || 0);
2253
+ const wanted = finite2(options.size) ? options.size : 240;
2254
+ const size = Math.max(120, width ? Math.min(wanted, width) : wanted);
2255
+ const outer = size / 2 - PULL - 2;
2256
+ geometry = { size, cx: size / 2, cy: size / 2, outer, inner: outer * donut() };
2257
+ canvas.setAttribute("viewBox", `0 0 ${size} ${size}`);
2258
+ canvas.setAttribute("width", String(size));
2259
+ canvas.setAttribute("height", String(size));
2260
+ plot.style.setProperty("--mk-pie-size", `${size}px`);
2261
+ };
2262
+ scope.add(() => plot.style.removeProperty("--mk-pie-size"));
2263
+ const paint = (angles) => {
2264
+ shown = angles;
2265
+ const { cx, cy, outer, inner } = geometry;
2266
+ paths.forEach((path, i) => {
2267
+ const [a0, a1] = angles[i] ?? [0, 0];
2268
+ path.setAttribute("d", a1 - a0 > 1e-4 ? arcPath(cx, cy, outer, inner, a0, a1) : "");
2269
+ });
2270
+ };
2271
+ const build = () => {
2272
+ canvas.replaceChildren();
2273
+ const group = svg2("g", { "data-mk-part": "series" });
2274
+ paths = model.map((slice, i) => {
2275
+ const path = svg2("path", { "data-mk-index": i });
2276
+ path.style.fill = slice.color;
2277
+ group.append(path);
2278
+ return path;
2279
+ });
2280
+ canvas.append(group);
2281
+ };
2282
+ const paintCenter = (animate) => {
2283
+ center.hidden = donut() < 0.35 || !model.length;
2284
+ if (center.hidden) return;
2285
+ const slice = selected === null ? null : model[selected];
2286
+ const text = slice ? format(slice.value) : format(total());
2287
+ const caption = slice ? `${slice.label} \xB7 ${share(slice.value)}` : options.totalLabel ?? "Total";
2288
+ const changed = centerLabel.textContent !== caption;
2289
+ if (centerValue.textContent !== text) {
2290
+ if (animate && !reduced() && centerValue.textContent) keep(rollDigits(centerValue, text, 1, 0.35));
2291
+ else centerValue.textContent = text;
2292
+ }
2293
+ centerLabel.textContent = caption;
2294
+ if (changed && animate && !reduced()) keep(blurIn(centerLabel, { from: "soft", duration: 0.3 }));
2295
+ };
2296
+ const paintSelection = (announce) => {
2297
+ const { outer } = geometry;
2298
+ paths.forEach((path, i) => {
2299
+ const chosen = i === selected;
2300
+ path.toggleAttribute("data-mk-dim", selected !== null && !chosen);
2301
+ if (!chosen) {
2302
+ path.style.transform = "";
2303
+ return;
2304
+ }
2305
+ const [a0, a1] = shown[i] ?? [0, 0];
2306
+ const [dx, dy] = polar(0, 0, Math.min(PULL, outer * 0.06), (a0 + a1) / 2);
2307
+ path.style.transform = `translate(${round(dx)}px, ${round(dy)}px)`;
2308
+ });
2309
+ legendItems.forEach((item, i) => item.toggleAttribute("data-mk-dim", selected !== null && i !== selected));
2310
+ paintCenter(true);
2311
+ const slice = selected === null ? null : model[selected];
2312
+ if (slice && center.hidden) {
2313
+ const [a0, a1] = shown[selected] ?? [0, 0];
2314
+ const [x, y] = polar(geometry.cx, geometry.cy, geometry.outer * 0.7, (a0 + a1) / 2);
2315
+ frame.tip({ x, y, title: slice.label, rows: [{ name: share(slice.value), color: slice.color, value: format(slice.value) }] });
2316
+ } else frame.tip(null);
2317
+ if (announce && slice) frame.live.textContent = `${slice.label}: ${format(slice.value)}, ${share(slice.value)}`;
2318
+ };
2319
+ let legendItems = [];
2320
+ const draw = (animate) => {
2321
+ glide?.kill();
2322
+ const before = shown;
2323
+ const sameCount = paths.length === model.length;
2324
+ const name = options.label ?? "Gr\xE1fica circular";
2325
+ canvas.setAttribute("aria-label", model.length ? `${name}. ${model.length} partes, total ${format(total())}. Usa las flechas para recorrerlas.` : `${name}: ${options.emptyLabel ?? "sin datos"}`);
2326
+ frame.fillTable(name, ["Parte", "Valor", "Porcentaje"], model.map((slice) => [slice.label, format(slice.value), share(slice.value)]));
2327
+ legendItems = frame.fillLegend(model.map((slice) => ({ name: slice.label, color: slice.color })), options.legend ?? true);
2328
+ legendItems.forEach((item, i) => {
2329
+ item.addEventListener("pointerenter", () => select(i));
2330
+ item.addEventListener("pointerleave", () => select(null));
2331
+ });
2332
+ frame.empty.hidden = model.length > 0;
2333
+ frame.empty.textContent = options.emptyLabel ?? "Sin datos para mostrar";
2334
+ plot.hidden = !model.length;
2335
+ if (!model.length) {
2336
+ canvas.replaceChildren();
2337
+ paths = [];
2338
+ shown = [];
2339
+ selected = null;
2340
+ return;
2341
+ }
2342
+ if (selected !== null && selected >= model.length) selected = null;
2343
+ measure();
2344
+ if (!sameCount || !before.length) build();
2345
+ paths.forEach((path, i) => {
2346
+ path.style.fill = model[i].color;
2347
+ });
2348
+ const target = anglesOf(model.map((slice) => slice.value));
2349
+ const done = () => {
2350
+ paint(target);
2351
+ paintSelection(false);
2352
+ };
2353
+ if (!animate || reduced()) return done();
2354
+ const state = { t: 0 };
2355
+ if (sameCount && before.length) {
2356
+ glide = gsap.to(state, {
2357
+ t: 1,
2358
+ duration: 0.6,
2359
+ ease: EASE.swatch,
2360
+ onUpdate: () => paint(target.map(([a0, a1], i) => [before[i][0] + (a0 - before[i][0]) * state.t, before[i][1] + (a1 - before[i][1]) * state.t])),
2361
+ onComplete: done
2362
+ });
2363
+ paintCenter(true);
2364
+ return;
2365
+ }
2366
+ paint(target.map(() => [0, 0]));
2367
+ centerValue.textContent = format(0);
2368
+ glide = gsap.to(state, {
2369
+ t: 1,
2370
+ duration: 0.9,
2371
+ ease: EASE.signature,
2372
+ onUpdate: () => paint(target.map(([a0, a1]) => [a0 * state.t, a1 * state.t])),
2373
+ onComplete: done
2374
+ });
2375
+ paintCenter(true);
2376
+ };
2377
+ const select = (index, announce = false) => {
2378
+ if (scope.disposed) return;
2379
+ const next = index === null || !model.length ? null : (Math.round(index) % model.length + model.length) % model.length;
2380
+ if (next === selected) return;
2381
+ selected = next;
2382
+ paintSelection(announce);
2383
+ options.onSelect?.(selected);
2384
+ const slice = selected === null ? null : model[selected];
2385
+ root.dispatchEvent(new CustomEvent("mk:select", { bubbles: true, detail: { index: selected, label: slice?.label ?? null, value: slice?.value ?? null } }));
2386
+ };
2387
+ const indexAt = (event) => {
2388
+ const target = event.target.closest("[data-mk-index]");
2389
+ return target ? Number(target.getAttribute("data-mk-index")) : null;
2390
+ };
2391
+ scope.on(canvas, "pointermove", (event) => select(indexAt(event)));
2392
+ scope.on(canvas, "pointerdown", (event) => select(indexAt(event)));
2393
+ scope.on(canvas, "pointerleave", (event) => {
2394
+ if (event.pointerType === "mouse") select(null);
2395
+ });
2396
+ scope.on(canvas, "focus", () => {
2397
+ if (selected === null && model.length) select(0, true);
2398
+ });
2399
+ scope.on(canvas, "blur", () => select(null));
2400
+ scope.on(canvas, "keydown", (event) => {
2401
+ const key = event.key;
2402
+ const moves = { ArrowRight: (selected ?? -1) + 1, ArrowDown: (selected ?? -1) + 1, ArrowLeft: (selected ?? 1) - 1, ArrowUp: (selected ?? 1) - 1, Home: 0, End: model.length - 1, Escape: null };
2403
+ if (!(key in moves) || !model.length) return;
2404
+ event.preventDefault();
2405
+ select(moves[key], true);
2406
+ });
2407
+ onWidth(root, scope, () => {
2408
+ measure();
2409
+ paint(shown);
2410
+ paintSelection(false);
2411
+ });
2412
+ draw(true);
2413
+ return {
2414
+ update(next) {
2415
+ if (scope.disposed) return;
2416
+ options = { ...options, ...next };
2417
+ model = slices();
2418
+ draw(true);
2419
+ },
2420
+ select: (index) => select(index),
2421
+ get selected() {
2422
+ return selected;
2423
+ },
2424
+ destroy: () => scope.dispose()
2425
+ };
2426
+ }
2427
+
2428
+ // src/components/radar-chart/engine.ts
2429
+ function niceMax(value) {
2430
+ if (value <= 0) return 1;
2431
+ const power = 10 ** Math.floor(Math.log10(value));
2432
+ const fraction = value / power;
2433
+ return (fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 2.5 ? 2.5 : fraction <= 5 ? 5 : 10) * power;
2434
+ }
2435
+ function createRadarChart(root, initial = {}) {
2436
+ if (!root) return inert({ update() {
2437
+ }, select() {
2438
+ }, selected: null });
2439
+ registerMotion();
2440
+ const scope = createScope();
2441
+ const frame = chartFrame(root, scope, "radar");
2442
+ const { plot, canvas, keep } = frame;
2443
+ let options = { ...initial };
2444
+ let selected = null;
2445
+ let focusSeries = null;
2446
+ const format = (value) => options.formatValue ? options.formatValue(value) : formatNumber(value);
2447
+ const modelOf = () => {
2448
+ const axes = (options.axes ?? []).map(String);
2449
+ const raw = options.series ?? [];
2450
+ return {
2451
+ axes,
2452
+ series: raw.map((series, i) => ({
2453
+ name: String(series.name ?? ""),
2454
+ values: axes.map((_, k) => finite2(series.values[k]) ? Math.max(0, series.values[k]) : null),
2455
+ color: series.color ?? slotColor(i, raw.length)
2456
+ }))
2457
+ };
2458
+ };
2459
+ let model = modelOf();
2460
+ const hasData = () => model.axes.length >= 3 && model.series.some((series) => series.values.some(finite2));
2461
+ const maxOf = () => finite2(options.max) && options.max > 0 ? options.max : niceMax(Math.max(0, ...model.series.flatMap((series) => series.values.filter(finite2))));
2462
+ let geometry = { size: 300, cx: 150, cy: 150, r: 110 };
2463
+ const angle = (k) => k / model.axes.length * Math.PI * 2;
2464
+ let shapes = [];
2465
+ let guide = null;
2466
+ let labels = [];
2467
+ let shown = [];
2468
+ let glide = null;
2469
+ scope.add(() => glide?.kill());
2470
+ const measure = () => {
2471
+ const width = Math.round(plot.clientWidth || root.clientWidth || 0);
2472
+ const wanted = finite2(options.size) ? options.size : 300;
2473
+ const size = Math.max(180, width ? Math.min(wanted, width) : wanted);
2474
+ const r = size / 2 - Math.min(64, Math.max(36, Math.max(...model.axes.map((axis) => axis.length)) * 3.6 + 14));
2475
+ geometry = { size, cx: size / 2, cy: size / 2, r: Math.max(40, r) };
2476
+ canvas.setAttribute("viewBox", `0 0 ${size} ${size}`);
2477
+ canvas.setAttribute("width", String(size));
2478
+ canvas.setAttribute("height", String(size));
2479
+ plot.style.setProperty("--mk-radar-size", `${size}px`);
2480
+ };
2481
+ scope.add(() => plot.style.removeProperty("--mk-radar-size"));
2482
+ const pointOf = (value, k, max) => polar(geometry.cx, geometry.cy, Math.min(value, max) / max * geometry.r, angle(k));
2483
+ const build = () => {
2484
+ canvas.replaceChildren();
2485
+ const { cx, cy, r } = geometry;
2486
+ const n = model.axes.length;
2487
+ const levels = Math.max(1, Math.round(options.levels ?? 4));
2488
+ const grid = svg2("g", { "data-mk-part": "grid" });
2489
+ for (let level = 1; level <= levels; level++) {
2490
+ const radius = r * level / levels;
2491
+ if (options.grid === "circle") grid.append(svg2("circle", { cx, cy, r: round(radius) }));
2492
+ else {
2493
+ const points = Array.from({ length: n }, (_, k) => polar(cx, cy, radius, angle(k)).map(round).join(",")).join(" ");
2494
+ grid.append(svg2("polygon", { points }));
2495
+ }
2496
+ }
2497
+ for (let k = 0; k < n; k++) {
2498
+ const [x, y] = polar(cx, cy, r, angle(k));
2499
+ grid.append(svg2("line", { x1: cx, y1: cy, x2: round(x), y2: round(y), "data-mk-spoke": k }));
2500
+ }
2501
+ canvas.append(grid);
2502
+ const names = svg2("g", { "data-mk-part": "axes" });
2503
+ labels = model.axes.map((axis, k) => {
2504
+ const a = angle(k);
2505
+ const [x, y] = polar(cx, cy, r + 14, a);
2506
+ const sin = Math.sin(a);
2507
+ const text = svg2("text", { x: round(x), y: round(y), "text-anchor": Math.abs(sin) < 0.2 ? "middle" : sin > 0 ? "start" : "end", "dominant-baseline": "middle" });
2508
+ text.textContent = axis;
2509
+ names.append(text);
2510
+ return text;
2511
+ });
2512
+ canvas.append(names);
2513
+ guide = svg2("line", { "data-mk-part": "guide" });
2514
+ guide.style.display = "none";
2515
+ canvas.append(guide);
2516
+ const series = svg2("g", { "data-mk-part": "series" });
2517
+ shapes = model.series.map((each, s) => {
2518
+ const group = svg2("g", { "data-mk-series": s });
2519
+ const area = svg2("path", { "data-mk-part": "area" });
2520
+ area.style.fill = each.color;
2521
+ const edge = svg2("path", { "data-mk-part": "edge" });
2522
+ edge.style.stroke = each.color;
2523
+ group.append(area, edge);
2524
+ const dots = model.axes.map(() => {
2525
+ const dot = svg2("circle", { r: 3, "data-mk-part": "dot" });
2526
+ dot.style.fill = each.color;
2527
+ group.append(dot);
2528
+ return dot;
2529
+ });
2530
+ series.append(group);
2531
+ return { area, edge, dots };
2532
+ });
2533
+ canvas.append(series);
2534
+ };
2535
+ const paint = (values) => {
2536
+ shown = values;
2537
+ const max = maxOf();
2538
+ shapes.forEach((shape, s) => {
2539
+ const list = values[s] ?? [];
2540
+ const points = model.axes.map((_, k) => pointOf(list[k] ?? 0, k, max));
2541
+ const d = `M${points.map(([x, y]) => `${round(x)},${round(y)}`).join("L")}Z`;
2542
+ shape.area.setAttribute("d", d);
2543
+ shape.edge.setAttribute("d", d);
2544
+ shape.dots.forEach((dot, k) => {
2545
+ dot.setAttribute("cx", String(round(points[k][0])));
2546
+ dot.setAttribute("cy", String(round(points[k][1])));
2547
+ dot.style.display = finite2(model.series[s].values[k]) ? "" : "none";
2548
+ });
2549
+ });
2550
+ };
2551
+ const paintSelection = (announce) => {
2552
+ shapes.forEach((shape, s) => {
2553
+ shape.area.parentElement.toggleAttribute("data-mk-dim", focusSeries !== null && s !== focusSeries);
2554
+ shape.dots.forEach((dot, k2) => {
2555
+ const on = k2 === selected;
2556
+ if (on === dot.hasAttribute("data-mk-on")) return;
2557
+ dot.toggleAttribute("data-mk-on", on);
2558
+ const r = on ? 5 : 3;
2559
+ if (reduced()) dot.setAttribute("r", String(r));
2560
+ else keep(gsap.to(dot, { attr: { r }, duration: on ? 0.3 : 0.2, ease: on ? EASE.pop : "power2.out" }));
2561
+ });
2562
+ });
2563
+ labels.forEach((label, k2) => label.toggleAttribute("data-mk-on", k2 === selected));
2564
+ if (selected === null || !guide) {
2565
+ if (guide) guide.style.display = "none";
2566
+ frame.tip(null);
2567
+ return;
2568
+ }
2569
+ const k = selected;
2570
+ const [x, y] = polar(geometry.cx, geometry.cy, geometry.r, angle(k));
2571
+ Object.entries({ x1: geometry.cx, y1: geometry.cy, x2: round(x), y2: round(y) }).forEach(([name, value]) => guide.setAttribute(name, String(value)));
2572
+ guide.style.display = "";
2573
+ const rows = model.series.map((series) => ({ name: series.name, color: series.color, value: finite2(series.values[k]) ? format(series.values[k]) : "\u2014" }));
2574
+ const max = maxOf();
2575
+ const top = Math.min(...model.series.map((series) => pointOf(series.values[k] ?? 0, k, max)[1]));
2576
+ frame.tip({ x: round(x), y: Math.min(round(y), top), title: model.axes[k], rows });
2577
+ if (announce) frame.live.textContent = `${model.axes[k]}. ${rows.map((row) => `${row.name ? `${row.name}: ` : ""}${row.value}`).join(". ")}`;
2578
+ };
2579
+ let legendItems = [];
2580
+ const draw = (animate) => {
2581
+ glide?.kill();
2582
+ const name = options.label ?? "Gr\xE1fica de radar";
2583
+ canvas.setAttribute("aria-label", hasData() ? `${name}. ${model.axes.length} medidas: ${model.axes.join(", ")}. Series: ${model.series.map((series) => series.name).join(", ")}. Usa las flechas para recorrer las medidas.` : `${name}: ${options.emptyLabel ?? "sin datos"}`);
2584
+ frame.fillTable(name, ["Medida", ...model.series.map((series) => series.name || "Valor")], model.axes.map((axis, k) => [axis, ...model.series.map((series) => finite2(series.values[k]) ? format(series.values[k]) : "\u2014")]));
2585
+ legendItems = frame.fillLegend(model.series, options.legend ?? model.series.length > 1, "area");
2586
+ legendItems.forEach((item, s) => {
2587
+ item.addEventListener("pointerenter", () => {
2588
+ focusSeries = s;
2589
+ paintSelection(false);
2590
+ });
2591
+ item.addEventListener("pointerleave", () => {
2592
+ focusSeries = null;
2593
+ paintSelection(false);
2594
+ });
2595
+ });
2596
+ const ok = hasData();
2597
+ frame.empty.hidden = ok;
2598
+ frame.empty.textContent = options.emptyLabel ?? "Sin datos para mostrar";
2599
+ plot.hidden = !ok;
2600
+ if (!ok) {
2601
+ canvas.replaceChildren();
2602
+ shapes = [];
2603
+ shown = [];
2604
+ selected = null;
2605
+ return;
2606
+ }
2607
+ if (selected !== null && selected >= model.axes.length) selected = null;
2608
+ const before = shown;
2609
+ const comparable = before.length === model.series.length && before.every((list) => list.length === model.axes.length) && shapes.length === model.series.length;
2610
+ measure();
2611
+ build();
2612
+ const target = model.series.map((series) => series.values.map((value) => value ?? 0));
2613
+ const done = () => {
2614
+ paint(target);
2615
+ paintSelection(false);
2616
+ };
2617
+ if (!animate || reduced()) return done();
2618
+ const state = { t: 0 };
2619
+ if (comparable) {
2620
+ glide = gsap.to(state, { t: 1, duration: 0.6, ease: EASE.swatch, onUpdate: () => paint(target.map((list, s) => list.map((value, k) => before[s][k] + (value - before[s][k]) * state.t))), onComplete: done });
2621
+ return;
2622
+ }
2623
+ const stagger = 0.09;
2624
+ const total = 0.7 + stagger * (target.length - 1);
2625
+ paint(target.map((list) => list.map(() => 0)));
2626
+ glide = gsap.to(state, {
2627
+ t: total,
2628
+ duration: total,
2629
+ ease: "none",
2630
+ onUpdate: () => paint(target.map((list, s) => {
2631
+ const local = Math.min(1, Math.max(0, (state.t - s * stagger) / 0.7));
2632
+ const eased2 = gsap.parseEase(EASE.swatch)(local);
2633
+ return list.map((value) => value * eased2);
2634
+ })),
2635
+ onComplete: done
2636
+ });
2637
+ };
2638
+ const select = (axis, announce = false) => {
2639
+ if (scope.disposed) return;
2640
+ const n = model.axes.length;
2641
+ const next = axis === null || !hasData() ? null : (Math.round(axis) % n + n) % n;
2642
+ if (next === selected) return;
2643
+ selected = next;
2644
+ paintSelection(announce);
2645
+ options.onSelect?.(selected);
2646
+ root.dispatchEvent(new CustomEvent("mk:select", { bubbles: true, detail: { index: selected, label: selected === null ? null : model.axes[selected], values: selected === null ? [] : model.series.map((series) => series.values[selected]) } }));
2647
+ };
2648
+ const axisAt = (event) => {
2649
+ const box = canvas.getBoundingClientRect();
2650
+ const scale = box.width ? geometry.size / box.width : 1;
2651
+ const x = (event.clientX - box.left) * scale - geometry.cx;
2652
+ const y = (event.clientY - box.top) * scale - geometry.cy;
2653
+ if (Math.hypot(x, y) < geometry.r * 0.12) return selected;
2654
+ const a = (Math.atan2(x, -y) + Math.PI * 2) % (Math.PI * 2);
2655
+ return Math.round(a / (Math.PI * 2 / model.axes.length)) % model.axes.length;
2656
+ };
2657
+ scope.on(canvas, "pointermove", (event) => select(axisAt(event)));
2658
+ scope.on(canvas, "pointerdown", (event) => select(axisAt(event)));
2659
+ scope.on(canvas, "pointerleave", (event) => {
2660
+ if (event.pointerType === "mouse") select(null);
2661
+ });
2662
+ scope.on(canvas, "focus", () => {
2663
+ if (selected === null && hasData()) select(0, true);
2664
+ });
2665
+ scope.on(canvas, "blur", () => select(null));
2666
+ scope.on(canvas, "keydown", (event) => {
2667
+ const key = event.key;
2668
+ const moves = { ArrowRight: (selected ?? -1) + 1, ArrowDown: (selected ?? -1) + 1, ArrowLeft: (selected ?? 1) - 1, ArrowUp: (selected ?? 1) - 1, Home: 0, End: model.axes.length - 1, Escape: null };
2669
+ if (!(key in moves) || !hasData()) return;
2670
+ event.preventDefault();
2671
+ select(moves[key], true);
2672
+ });
2673
+ onWidth(root, scope, () => {
2674
+ if (!hasData()) return;
2675
+ measure();
2676
+ build();
2677
+ paint(shown);
2678
+ paintSelection(false);
2679
+ });
2680
+ draw(true);
2681
+ return {
2682
+ update(next) {
2683
+ if (scope.disposed) return;
2684
+ options = { ...options, ...next };
2685
+ model = modelOf();
2686
+ draw(true);
2687
+ },
2688
+ select: (axis) => select(axis),
2689
+ get selected() {
2690
+ return selected;
2691
+ },
2692
+ destroy: () => scope.dispose()
2693
+ };
2694
+ }
2695
+
2696
+ // src/components/radial-chart/engine.ts
2697
+ function strokeArc(cx, cy, r, a0, a1) {
2698
+ const end = a0 + Math.min(a1 - a0, Math.PI * 2 - 1e-3);
2699
+ const [x0, y0] = polar(cx, cy, r, a0);
2700
+ const [x1, y1] = polar(cx, cy, r, end);
2701
+ return `M${round(x0)},${round(y0)}A${round(r)},${round(r)} 0 ${end - a0 > Math.PI ? 1 : 0} 1 ${round(x1)},${round(y1)}`;
2702
+ }
2703
+ function createRadialChart(root, initial = {}) {
2704
+ if (!root) return inert({ update() {
2705
+ }, select() {
2706
+ }, selected: null });
2707
+ registerMotion();
2708
+ const scope = createScope();
2709
+ const frame = chartFrame(root, scope, "radial");
2710
+ const { plot, canvas, keep } = frame;
2711
+ let options = { ...initial };
2712
+ let selected = null;
2713
+ const center = document.createElement("div");
2714
+ center.dataset.mkRadialCenter = "";
2715
+ center.setAttribute("aria-hidden", "true");
2716
+ const centerValue = document.createElement("strong");
2717
+ const centerLabel = document.createElement("span");
2718
+ center.append(centerValue, centerLabel);
2719
+ plot.append(center);
2720
+ scope.add(() => center.remove());
2721
+ const format = (value) => options.formatValue ? options.formatValue(value) : formatNumber(value);
2722
+ const modelOf = () => {
2723
+ const data = (options.data ?? []).filter((datum) => finite2(datum.value));
2724
+ const top = Math.max(0, ...data.map((datum) => datum.value));
2725
+ return data.map((datum, i) => ({
2726
+ label: String(datum.label),
2727
+ value: Math.max(0, datum.value),
2728
+ max: finite2(datum.max) && datum.max > 0 ? datum.max : finite2(options.max) && options.max > 0 ? options.max : top || 1,
2729
+ color: datum.color ?? slotColor(i, Math.max(2, data.length))
2730
+ }));
2731
+ };
2732
+ let model = modelOf();
2733
+ const total = () => model.reduce((sum, ring) => sum + ring.value, 0);
2734
+ const percent = (ring) => new Intl.NumberFormat(void 0, { style: "percent", maximumFractionDigits: 0 }).format(ring.value / ring.max);
2735
+ const sweep = () => Math.min(360, Math.max(90, finite2(options.sweep) ? options.sweep : 360)) * Math.PI / 180;
2736
+ const startAngle = () => -sweep() / 2 + (sweep() >= Math.PI * 2 - 1e-6 ? Math.PI : 0);
2737
+ let geometry = { size: 240, cx: 120, cy: 120, radii: [], thickness: 12 };
2738
+ let arcs = [];
2739
+ let tracks = [];
2740
+ let shown = [];
2741
+ let glide = null;
2742
+ scope.add(() => glide?.kill());
2743
+ const measure = () => {
2744
+ const width = Math.round(plot.clientWidth || root.clientWidth || 0);
2745
+ const wanted = finite2(options.size) ? options.size : 240;
2746
+ const size = Math.max(120, width ? Math.min(wanted, width) : wanted);
2747
+ const n = Math.max(1, model.length);
2748
+ const outer = size / 2 - 4;
2749
+ const fit = outer * 0.6 / (n + (n - 1) * 0.35);
2750
+ const thickness = finite2(options.thickness) ? options.thickness : Math.max(4, Math.min(16, fit));
2751
+ const spacing = thickness * 1.35;
2752
+ geometry = { size, cx: size / 2, cy: size / 2, thickness, radii: model.map((_, i) => outer - thickness / 2 - i * spacing) };
2753
+ canvas.setAttribute("viewBox", `0 0 ${size} ${size}`);
2754
+ canvas.setAttribute("width", String(size));
2755
+ canvas.setAttribute("height", String(size));
2756
+ plot.style.setProperty("--mk-radial-size", `${size}px`);
2757
+ plot.style.setProperty("--mk-radial-thickness", `${thickness}px`);
2758
+ };
2759
+ scope.add(() => {
2760
+ plot.style.removeProperty("--mk-radial-size");
2761
+ plot.style.removeProperty("--mk-radial-thickness");
2762
+ });
2763
+ const build = () => {
2764
+ canvas.replaceChildren();
2765
+ const { cx, cy, radii } = geometry;
2766
+ const a0 = startAngle();
2767
+ const back = svg2("g", { "data-mk-part": "tracks" });
2768
+ const front = svg2("g", { "data-mk-part": "series" });
2769
+ tracks = model.map((_, i) => {
2770
+ const track = svg2("path", { d: strokeArc(cx, cy, radii[i], a0, a0 + sweep()), "data-mk-index": i });
2771
+ back.append(track);
2772
+ return track;
2773
+ });
2774
+ arcs = model.map((ring, i) => {
2775
+ const arc = svg2("path", { "data-mk-index": i, "data-mk-part": "ring" });
2776
+ arc.style.stroke = ring.color;
2777
+ front.append(arc);
2778
+ return arc;
2779
+ });
2780
+ canvas.append(back, front);
2781
+ };
2782
+ const paint = (values) => {
2783
+ shown = values;
2784
+ const { cx, cy, radii } = geometry;
2785
+ const a0 = startAngle();
2786
+ arcs.forEach((arc, i) => {
2787
+ const share = Math.min(1, Math.max(0, (values[i] ?? 0) / model[i].max));
2788
+ arc.setAttribute("d", share > 1e-3 ? strokeArc(cx, cy, radii[i], a0, a0 + sweep() * share) : "");
2789
+ });
2790
+ };
2791
+ const paintCenter = (animate) => {
2792
+ center.hidden = !model.length;
2793
+ const ring = selected === null ? null : model[selected];
2794
+ const text = ring ? format(ring.value) : format(total());
2795
+ const caption = ring ? `${ring.label} \xB7 ${percent(ring)}` : options.totalLabel ?? "Total";
2796
+ const changed = centerLabel.textContent !== caption;
2797
+ if (centerValue.textContent !== text) {
2798
+ if (animate && !reduced() && centerValue.textContent) keep(rollDigits(centerValue, text, 1, 0.35));
2799
+ else centerValue.textContent = text;
2800
+ }
2801
+ centerLabel.textContent = caption;
2802
+ if (changed && animate && !reduced()) keep(blurIn(centerLabel, { from: "soft", duration: 0.3 }));
2803
+ };
2804
+ let legendItems = [];
2805
+ const paintSelection = (announce) => {
2806
+ for (const list of [arcs, tracks]) list.forEach((each, i) => {
2807
+ each.toggleAttribute("data-mk-on", i === selected);
2808
+ each.toggleAttribute("data-mk-dim", selected !== null && i !== selected);
2809
+ });
2810
+ legendItems.forEach((item, i) => item.toggleAttribute("data-mk-dim", selected !== null && i !== selected));
2811
+ paintCenter(true);
2812
+ const ring = selected === null ? null : model[selected];
2813
+ if (announce && ring) frame.live.textContent = `${ring.label}: ${format(ring.value)} de ${format(ring.max)}, ${percent(ring)}`;
2814
+ };
2815
+ const draw = (animate) => {
2816
+ glide?.kill();
2817
+ const name = options.label ?? "Gr\xE1fica radial";
2818
+ canvas.setAttribute("aria-label", model.length ? `${name}. ${model.length} anillos, total ${format(total())}. Usa las flechas para recorrerlos.` : `${name}: ${options.emptyLabel ?? "sin datos"}`);
2819
+ frame.fillTable(name, ["Anillo", "Valor", "Meta", "Avance"], model.map((ring) => [ring.label, format(ring.value), format(ring.max), percent(ring)]));
2820
+ legendItems = frame.fillLegend(model.map((datum) => ({ name: datum.label, color: datum.color })), options.legend ?? true);
2821
+ legendItems.forEach((item, i) => {
2822
+ item.addEventListener("pointerenter", () => select(i));
2823
+ item.addEventListener("pointerleave", () => select(null));
2824
+ });
2825
+ frame.empty.hidden = model.length > 0;
2826
+ frame.empty.textContent = options.emptyLabel ?? "Sin datos para mostrar";
2827
+ plot.hidden = !model.length;
2828
+ if (!model.length) {
2829
+ canvas.replaceChildren();
2830
+ arcs = [];
2831
+ shown = [];
2832
+ selected = null;
2833
+ return;
2834
+ }
2835
+ if (selected !== null && selected >= model.length) selected = null;
2836
+ const before = shown;
2837
+ const comparable = before.length === model.length && arcs.length === model.length;
2838
+ measure();
2839
+ build();
2840
+ const target = model.map((ring) => ring.value);
2841
+ const done = () => {
2842
+ paint(target);
2843
+ paintSelection(false);
2844
+ };
2845
+ if (!animate || reduced()) return done();
2846
+ const state = { t: 0 };
2847
+ if (comparable) {
2848
+ glide = gsap.to(state, { t: 1, duration: 0.6, ease: EASE.swatch, onUpdate: () => paint(target.map((value, i) => before[i] + (value - before[i]) * state.t)), onComplete: done });
2849
+ paintCenter(true);
2850
+ return;
2851
+ }
2852
+ const stagger = 0.08;
2853
+ const length = 0.8 + stagger * (target.length - 1);
2854
+ const ease = gsap.parseEase(EASE.swatch);
2855
+ paint(target.map(() => 0));
2856
+ centerValue.textContent = format(0);
2857
+ glide = gsap.to(state, {
2858
+ t: length,
2859
+ duration: length,
2860
+ ease: "none",
2861
+ onUpdate: () => paint(target.map((value, i) => value * ease(Math.min(1, Math.max(0, (state.t - i * stagger) / 0.8))))),
2862
+ onComplete: done
2863
+ });
2864
+ paintCenter(true);
2865
+ };
2866
+ const select = (index, announce = false) => {
2867
+ if (scope.disposed) return;
2868
+ const next = index === null || !model.length ? null : (Math.round(index) % model.length + model.length) % model.length;
2869
+ if (next === selected) return;
2870
+ selected = next;
2871
+ paintSelection(announce);
2872
+ options.onSelect?.(selected);
2873
+ const ring = selected === null ? null : model[selected];
2874
+ root.dispatchEvent(new CustomEvent("mk:select", { bubbles: true, detail: { index: selected, label: ring?.label ?? null, value: ring?.value ?? null } }));
2875
+ };
2876
+ const ringAt = (event) => {
2877
+ const box = canvas.getBoundingClientRect();
2878
+ const scale = box.width ? geometry.size / box.width : 1;
2879
+ const distance = Math.hypot((event.clientX - box.left) * scale - geometry.cx, (event.clientY - box.top) * scale - geometry.cy);
2880
+ const near = geometry.radii.findIndex((r) => Math.abs(distance - r) <= geometry.thickness * 0.7);
2881
+ return near < 0 ? null : near;
2882
+ };
2883
+ scope.on(canvas, "pointermove", (event) => select(ringAt(event)));
2884
+ scope.on(canvas, "pointerdown", (event) => select(ringAt(event)));
2885
+ scope.on(canvas, "pointerleave", (event) => {
2886
+ if (event.pointerType === "mouse") select(null);
2887
+ });
2888
+ scope.on(canvas, "focus", () => {
2889
+ if (selected === null && model.length) select(0, true);
2890
+ });
2891
+ scope.on(canvas, "blur", () => select(null));
2892
+ scope.on(canvas, "keydown", (event) => {
2893
+ const key = event.key;
2894
+ const moves = { ArrowDown: (selected ?? -1) + 1, ArrowRight: (selected ?? -1) + 1, ArrowUp: (selected ?? 1) - 1, ArrowLeft: (selected ?? 1) - 1, Home: 0, End: model.length - 1, Escape: null };
2895
+ if (!(key in moves) || !model.length) return;
2896
+ event.preventDefault();
2897
+ select(moves[key], true);
2898
+ });
2899
+ onWidth(root, scope, () => {
2900
+ if (!model.length) return;
2901
+ measure();
2902
+ build();
2903
+ paint(shown);
2904
+ paintSelection(false);
2905
+ });
2906
+ draw(true);
2907
+ return {
2908
+ update(next) {
2909
+ if (scope.disposed) return;
2910
+ options = { ...options, ...next };
2911
+ model = modelOf();
2912
+ draw(true);
2913
+ },
2914
+ select: (index) => select(index),
2915
+ get selected() {
2916
+ return selected;
2917
+ },
2918
+ destroy: () => scope.dispose()
2919
+ };
2920
+ }
2921
+
2922
+ // src/components/sankey-chart/engine.ts
2923
+ function createSankeyChart(root, initial = {}) {
2924
+ if (!root) return inert({ update() {
2925
+ }, select() {
2926
+ }, selected: null });
2927
+ registerMotion();
2928
+ const scope = createScope();
2929
+ const frame = chartFrame(root, scope, "sankey");
2930
+ const { plot, canvas } = frame;
2931
+ let options = { ...initial };
2932
+ let selected = null;
2933
+ let hoverNode = null;
2934
+ const uid = `mk-sankey-${Math.random().toString(36).slice(2, 8)}`;
2935
+ const format = (value) => options.formatValue ? options.formatValue(value) : formatNumber(value);
2936
+ const modelOf = () => {
2937
+ const nodes = (options.nodes ?? []).map((node, i, all) => ({ id: String(node.id), label: String(node.label ?? node.id), color: node.color ?? slotColor(i, Math.max(2, all.length)) }));
2938
+ const index = new Map(nodes.map((node, i) => [node.id, i]));
2939
+ const links = (options.links ?? []).map((link, i) => ({ source: index.get(String(link.source)) ?? -1, target: index.get(String(link.target)) ?? -1, value: link.value, given: i })).filter((link) => link.source >= 0 && link.target >= 0 && link.source !== link.target && finite2(link.value) && link.value > 0);
2940
+ const depth = nodes.map(() => 0);
2941
+ for (let pass = 0; pass < nodes.length; pass++) {
2942
+ let moved = false;
2943
+ for (const link of links) {
2944
+ if (depth[link.target] < depth[link.source] + 1 && depth[link.source] + 1 < nodes.length) {
2945
+ depth[link.target] = depth[link.source] + 1;
2946
+ moved = true;
2947
+ }
2948
+ }
2949
+ if (!moved) break;
2950
+ }
2951
+ const columns = Math.max(0, ...depth) + 1;
2952
+ nodes.forEach((_, i) => {
2953
+ if (links.some((link) => link.source === i) || !links.some((link) => link.target === i)) return;
2954
+ depth[i] = columns - 1;
2955
+ });
2956
+ const used = nodes.map((_, i) => links.some((link) => link.source === i || link.target === i));
2957
+ const value = nodes.map((_, i) => Math.max(links.filter((link) => link.target === i).reduce((s, l) => s + l.value, 0), links.filter((link) => link.source === i).reduce((s, l) => s + l.value, 0)));
2958
+ return { nodes, links, depth, columns, used, value };
2959
+ };
2960
+ let model = modelOf();
2961
+ const hasData = () => model.links.length > 0;
2962
+ let width = 600;
2963
+ let height = 320;
2964
+ const layoutOf = () => {
2965
+ width = Math.max(240, Math.round(plot.clientWidth || root.clientWidth || 600));
2966
+ height = Math.max(160, finite2(options.height) ? options.height : 320);
2967
+ const nodeWidth2 = Math.max(4, finite2(options.nodeWidth) ? options.nodeWidth : 12);
2968
+ const pad = Math.max(2, finite2(options.nodePadding) ? options.nodePadding : 14);
2969
+ const top = 6;
2970
+ const usable = height - top * 2;
2971
+ const columns = Array.from({ length: model.columns }, (_, c) => model.nodes.map((_2, i) => i).filter((i) => model.used[i] && model.depth[i] === c));
2972
+ const ky = Math.min(...columns.filter((list) => list.length).map((list) => (usable - pad * (list.length - 1)) / list.reduce((sum, i) => sum + model.value[i], 0)));
2973
+ const step = model.columns > 1 ? (width - nodeWidth2) / (model.columns - 1) : 0;
2974
+ const nodes = model.nodes.map(() => ({ x: 0, y0: 0, y1: 0 }));
2975
+ for (const list of columns) {
2976
+ const total = list.reduce((sum, i) => sum + model.value[i] * ky, 0) + pad * Math.max(0, list.length - 1);
2977
+ let y = top + (usable - total) / 2;
2978
+ for (const i of list) {
2979
+ const h = Math.max(1, model.value[i] * ky);
2980
+ nodes[i] = { x: model.depth[i] * step, y0: y, y1: y + h };
2981
+ y += h + pad;
2982
+ }
2983
+ }
2984
+ const out = /* @__PURE__ */ new Map();
2985
+ const into = /* @__PURE__ */ new Map();
2986
+ const centre = (i) => (nodes[i].y0 + nodes[i].y1) / 2;
2987
+ const links = model.links.map(() => ({ sy: 0, ty: 0, width: 0 }));
2988
+ const order = model.links.map((_, k) => k);
2989
+ for (const k of [...order].sort((a, b) => centre(model.links[a].target) - centre(model.links[b].target))) {
2990
+ const link = model.links[k];
2991
+ const w = link.value * ky;
2992
+ const at = out.get(link.source) ?? nodes[link.source].y0;
2993
+ links[k].sy = at + w / 2;
2994
+ links[k].width = w;
2995
+ out.set(link.source, at + w);
2996
+ }
2997
+ for (const k of [...order].sort((a, b) => centre(model.links[a].source) - centre(model.links[b].source))) {
2998
+ const link = model.links[k];
2999
+ const at = into.get(link.target) ?? nodes[link.target].y0;
3000
+ links[k].ty = at + links[k].width / 2;
3001
+ into.set(link.target, at + links[k].width);
3002
+ }
3003
+ return { nodes, links };
3004
+ };
3005
+ let shown = null;
3006
+ let rects = [];
3007
+ let bands = [];
3008
+ let names = [];
3009
+ let glide = null;
3010
+ scope.add(() => glide?.kill());
3011
+ const nodeWidth = () => Math.max(4, finite2(options.nodeWidth) ? options.nodeWidth : 12);
3012
+ const build = () => {
3013
+ canvas.replaceChildren();
3014
+ canvas.setAttribute("viewBox", `0 0 ${width} ${height}`);
3015
+ canvas.setAttribute("width", String(width));
3016
+ canvas.setAttribute("height", String(height));
3017
+ const defs = svg2("defs");
3018
+ const flows = svg2("g", { "data-mk-part": "links" });
3019
+ bands = model.links.map((link, k) => {
3020
+ const gradient = svg2("linearGradient", { id: `${uid}-${k}`, gradientUnits: "userSpaceOnUse" });
3021
+ const from = svg2("stop", { offset: "0" });
3022
+ from.style.stopColor = model.nodes[link.source].color;
3023
+ const to = svg2("stop", { offset: "1" });
3024
+ to.style.stopColor = model.nodes[link.target].color;
3025
+ gradient.append(from, to);
3026
+ defs.append(gradient);
3027
+ const band = svg2("path", { "data-mk-index": k });
3028
+ band.style.fill = `url(#${uid}-${k})`;
3029
+ flows.append(band);
3030
+ return band;
3031
+ });
3032
+ const nodesLayer = svg2("g", { "data-mk-part": "nodes" });
3033
+ rects = model.nodes.map((node, i) => {
3034
+ const rect = svg2("rect", { width: nodeWidth(), rx: 3, "data-mk-node": i });
3035
+ rect.style.fill = node.color;
3036
+ if (!model.used[i]) rect.style.display = "none";
3037
+ nodesLayer.append(rect);
3038
+ return rect;
3039
+ });
3040
+ const labels = svg2("g", { "data-mk-part": "labels" });
3041
+ names = model.nodes.map((node, i) => {
3042
+ const text = svg2("text", { "dominant-baseline": "middle" });
3043
+ text.textContent = node.label;
3044
+ const amount = document.createElementNS(SVG_NS, "tspan");
3045
+ amount.setAttribute("data-mk-part", "amount");
3046
+ amount.textContent = ` ${format(model.value[i])}`;
3047
+ text.append(amount);
3048
+ if (!model.used[i]) text.style.display = "none";
3049
+ labels.append(text);
3050
+ return text;
3051
+ });
3052
+ canvas.append(defs, flows, nodesLayer, labels);
3053
+ };
3054
+ const paint = (layout2) => {
3055
+ shown = layout2;
3056
+ const w = nodeWidth();
3057
+ layout2.nodes.forEach((node, i) => {
3058
+ const rect = rects[i];
3059
+ rect.setAttribute("x", String(round(node.x)));
3060
+ rect.setAttribute("y", String(round(node.y0)));
3061
+ rect.setAttribute("height", String(round(Math.max(1, node.y1 - node.y0))));
3062
+ const last = model.depth[i] === model.columns - 1 && model.columns > 1;
3063
+ const name = names[i];
3064
+ const first = model.depth[i] === 0;
3065
+ const x = round(last ? node.x - 8 : first ? node.x + w + 8 : node.x + w / 2);
3066
+ const available = model.columns > 1 ? (width - w) / (model.columns - 1) - 20 : width - w - 16;
3067
+ const characters = Math.max(3, Math.floor(available / 7));
3068
+ const label = model.nodes[i].label;
3069
+ if (name.firstChild) name.firstChild.textContent = label.length > characters ? `${label.slice(0, characters - 1)}\u2026` : label;
3070
+ name.setAttribute("x", String(x));
3071
+ name.setAttribute("y", String(round((node.y0 + node.y1) / 2 - 7)));
3072
+ name.setAttribute("text-anchor", last ? "end" : first ? "start" : "middle");
3073
+ const amount = name.querySelector("tspan");
3074
+ amount?.setAttribute("x", String(x));
3075
+ amount?.setAttribute("dy", "14");
3076
+ });
3077
+ layout2.links.forEach((link, k) => {
3078
+ const { source, target } = model.links[k];
3079
+ const x0 = layout2.nodes[source].x + w;
3080
+ const x1 = layout2.nodes[target].x;
3081
+ const xm = (x0 + x1) / 2;
3082
+ const h = link.width / 2;
3083
+ const [s0, s1, t0, t1] = [link.sy - h, link.sy + h, link.ty - h, link.ty + h].map(round);
3084
+ bands[k].setAttribute("d", `M${round(x0)},${s0}C${round(xm)},${s0} ${round(xm)},${t0} ${round(x1)},${t0}L${round(x1)},${t1}C${round(xm)},${t1} ${round(xm)},${s1} ${round(x0)},${s1}Z`);
3085
+ const gradient = canvas.querySelector(`#${uid}-${k}`);
3086
+ gradient?.setAttribute("x1", String(round(x0)));
3087
+ gradient?.setAttribute("x2", String(round(x1)));
3088
+ });
3089
+ };
3090
+ const share = (part2, whole) => new Intl.NumberFormat(void 0, { style: "percent", maximumFractionDigits: 1 }).format(part2 / (whole || 1));
3091
+ const paintSelection = (announce) => {
3092
+ const lit = (k) => selected !== null ? k === selected : hoverNode !== null ? model.links[k].source === hoverNode || model.links[k].target === hoverNode : false;
3093
+ const any = selected !== null || hoverNode !== null;
3094
+ bands.forEach((band, k) => {
3095
+ band.toggleAttribute("data-mk-on", lit(k));
3096
+ band.toggleAttribute("data-mk-dim", any && !lit(k));
3097
+ });
3098
+ rects.forEach((rect, i) => rect.toggleAttribute("data-mk-dim", any && !model.links.some((link, k) => lit(k) && (link.source === i || link.target === i))));
3099
+ if (!shown || selected === null && hoverNode === null) {
3100
+ frame.tip(null);
3101
+ return;
3102
+ }
3103
+ if (selected !== null) {
3104
+ const link = model.links[selected];
3105
+ const geometry = shown.links[selected];
3106
+ const x = (shown.nodes[link.source].x + shown.nodes[link.target].x + nodeWidth()) / 2;
3107
+ const y = (geometry.sy + geometry.ty) / 2 - geometry.width / 2;
3108
+ const from = model.nodes[link.source];
3109
+ const to = model.nodes[link.target];
3110
+ const rows = [{ name: `${share(link.value, model.value[link.source])} de ${from.label}`, color: from.color, value: format(link.value) }];
3111
+ frame.tip({ x: round(x), y: round(y), title: `${from.label} \u2192 ${to.label}`, rows });
3112
+ if (announce) frame.live.textContent = `${from.label} a ${to.label}: ${format(link.value)}`;
3113
+ } else {
3114
+ const i = hoverNode;
3115
+ const node = shown.nodes[i];
3116
+ frame.tip({ x: round(node.x + nodeWidth() / 2), y: round(node.y0), title: model.nodes[i].label, rows: [{ name: "Total", color: model.nodes[i].color, value: format(model.value[i]) }] });
3117
+ }
3118
+ };
3119
+ const draw = (animate) => {
3120
+ glide?.kill();
3121
+ const name = options.label ?? "Diagrama de flujo";
3122
+ canvas.setAttribute("aria-label", hasData() ? `${name}. ${model.links.length} flujos entre ${model.used.filter(Boolean).length} etapas. Usa las flechas para recorrer los flujos.` : `${name}: ${options.emptyLabel ?? "sin datos"}`);
3123
+ frame.fillTable(name, ["Flujo", "Valor"], model.links.map((link) => [`${model.nodes[link.source].label} \u2192 ${model.nodes[link.target].label}`, format(link.value)]));
3124
+ frame.fillLegend([], false);
3125
+ const ok = hasData();
3126
+ frame.empty.hidden = ok;
3127
+ frame.empty.textContent = options.emptyLabel ?? "Sin datos para mostrar";
3128
+ plot.hidden = !ok;
3129
+ if (!ok) {
3130
+ canvas.replaceChildren();
3131
+ shown = null;
3132
+ selected = null;
3133
+ return;
3134
+ }
3135
+ if (selected !== null && selected >= model.links.length) selected = null;
3136
+ const before = shown;
3137
+ const target = layoutOf();
3138
+ const comparable = before && before.nodes.length === target.nodes.length && before.links.length === target.links.length;
3139
+ build();
3140
+ const done = () => {
3141
+ paint(target);
3142
+ paintSelection(false);
3143
+ };
3144
+ if (!animate || reduced()) return done();
3145
+ if (comparable) {
3146
+ const state = { t: 0 };
3147
+ const mix = (a, b) => a + (b - a) * state.t;
3148
+ glide = gsap.to(state, {
3149
+ t: 1,
3150
+ duration: 0.6,
3151
+ ease: EASE.swatch,
3152
+ onUpdate: () => paint({
3153
+ nodes: target.nodes.map((node, i) => ({ x: mix(before.nodes[i].x, node.x), y0: mix(before.nodes[i].y0, node.y0), y1: mix(before.nodes[i].y1, node.y1) })),
3154
+ links: target.links.map((link, k) => ({ sy: mix(before.links[k].sy, link.sy), ty: mix(before.links[k].ty, link.ty), width: mix(before.links[k].width, link.width) }))
3155
+ }),
3156
+ onComplete: done
3157
+ });
3158
+ return;
3159
+ }
3160
+ done();
3161
+ const timeline = gsap.timeline();
3162
+ rects.forEach((rect2, i) => {
3163
+ timeline.fromTo(rect2, { scaleY: 0, transformOrigin: "50% 50%" }, { scaleY: 1, duration: 0.5, ease: EASE.swatch, clearProps: "transform" }, (model.depth[i] ?? 0) * 0.12);
3164
+ });
3165
+ timeline.fromTo(names, { opacity: 0 }, { opacity: 1, duration: 0.4, stagger: 0.02, clearProps: "opacity" }, 0.2);
3166
+ const clip = svg2("clipPath", { id: `${uid}-wipe` });
3167
+ const rect = svg2("rect", { x: 0, y: 0, width: 0, height });
3168
+ clip.append(rect);
3169
+ canvas.prepend(clip);
3170
+ const flows = canvas.querySelector('[data-mk-part="links"]');
3171
+ flows.setAttribute("clip-path", `url(#${uid}-wipe)`);
3172
+ timeline.to(rect, { attr: { width }, duration: 0.9, ease: EASE.wipe, onComplete: () => {
3173
+ flows.removeAttribute("clip-path");
3174
+ clip.remove();
3175
+ } }, 0.1);
3176
+ glide = timeline;
3177
+ };
3178
+ const select = (index, announce = false) => {
3179
+ if (scope.disposed) return;
3180
+ const n = model.links.length;
3181
+ const next = index === null || !n ? null : (Math.round(index) % n + n) % n;
3182
+ if (next === selected) return;
3183
+ selected = next;
3184
+ paintSelection(announce);
3185
+ options.onSelect?.(selected === null ? null : model.links[selected].given);
3186
+ const link = selected === null ? null : model.links[selected];
3187
+ root.dispatchEvent(new CustomEvent("mk:select", { bubbles: true, detail: { index: link?.given ?? null, source: link ? model.nodes[link.source].id : null, target: link ? model.nodes[link.target].id : null, value: link?.value ?? null } }));
3188
+ };
3189
+ scope.on(canvas, "pointermove", (event) => {
3190
+ const target = event.target;
3191
+ const band = target.closest("[data-mk-index]");
3192
+ const node = target.closest("[data-mk-node]");
3193
+ if (node) {
3194
+ const i = Number(node.getAttribute("data-mk-node"));
3195
+ if (selected !== null) select(null);
3196
+ if (hoverNode !== i) {
3197
+ hoverNode = i;
3198
+ paintSelection(false);
3199
+ }
3200
+ return;
3201
+ }
3202
+ if (hoverNode !== null) {
3203
+ hoverNode = null;
3204
+ paintSelection(false);
3205
+ }
3206
+ select(band ? Number(band.getAttribute("data-mk-index")) : null);
3207
+ });
3208
+ scope.on(canvas, "pointerleave", (event) => {
3209
+ if (event.pointerType !== "mouse") return;
3210
+ hoverNode = null;
3211
+ select(null);
3212
+ paintSelection(false);
3213
+ });
3214
+ scope.on(canvas, "focus", () => {
3215
+ if (selected === null && hasData()) select(0, true);
3216
+ });
3217
+ scope.on(canvas, "blur", () => select(null));
3218
+ scope.on(canvas, "keydown", (event) => {
3219
+ const key = event.key;
3220
+ const moves = { ArrowDown: (selected ?? -1) + 1, ArrowRight: (selected ?? -1) + 1, ArrowUp: (selected ?? 1) - 1, ArrowLeft: (selected ?? 1) - 1, Home: 0, End: model.links.length - 1, Escape: null };
3221
+ if (!(key in moves) || !hasData()) return;
3222
+ event.preventDefault();
3223
+ select(moves[key], true);
3224
+ });
3225
+ onWidth(root, scope, () => {
3226
+ if (!hasData()) return;
3227
+ const layout2 = layoutOf();
3228
+ build();
3229
+ paint(layout2);
3230
+ paintSelection(false);
3231
+ });
3232
+ draw(true);
3233
+ return {
3234
+ update(next) {
3235
+ if (scope.disposed) return;
3236
+ options = { ...options, ...next };
3237
+ model = modelOf();
3238
+ draw(true);
3239
+ },
3240
+ select: (index) => {
3241
+ const at = index === null ? null : model.links.findIndex((link) => link.given === index);
3242
+ select(at === null || at < 0 ? null : at);
3243
+ },
3244
+ get selected() {
3245
+ return selected === null ? null : model.links[selected].given;
3246
+ },
3247
+ destroy: () => scope.dispose()
3248
+ };
3249
+ }
3250
+
3251
+ // src/components/heatmap-chart/engine.ts
3252
+ var LEFT = 28;
3253
+ var TOP = 18;
3254
+ function createHeatmapChart(root, initial = {}) {
3255
+ if (!root) return inert({ update() {
3256
+ }, select() {
3257
+ }, selected: null });
3258
+ registerMotion();
3259
+ const scope = createScope();
3260
+ const frame = chartFrame(root, scope, "heatmap");
3261
+ const { plot, canvas, keep } = frame;
3262
+ let options = { ...initial };
3263
+ let selected = null;
3264
+ const scroller = document.createElement("div");
3265
+ scroller.dataset.mkHeatmapScroll = "";
3266
+ plot.prepend(scroller);
3267
+ scroller.append(canvas);
3268
+ scope.add(() => scroller.remove());
3269
+ const scale = document.createElement("div");
3270
+ scale.dataset.mkHeatmapScale = "";
3271
+ scale.setAttribute("aria-hidden", "true");
3272
+ root.insertBefore(scale, frame.legend);
3273
+ scope.add(() => scale.remove());
3274
+ const locale = () => options.locale ?? (document.documentElement.lang || "es");
3275
+ const format = (value) => options.formatValue ? options.formatValue(value) : formatNumber(value);
3276
+ const levels = () => Math.max(1, Math.min(8, Math.round(options.levels ?? 4)));
3277
+ const modelOf = () => {
3278
+ const values = /* @__PURE__ */ new Map();
3279
+ const add = (key, value) => finite2(value) && values.set(key, (values.get(key) ?? 0) + value);
3280
+ if (Array.isArray(options.data)) for (const datum of options.data) add(dayKey(toDate(datum.date)), datum.value);
3281
+ else if (options.data) for (const [key, value] of Object.entries(options.data)) add(dayKey(toDate(key)), value);
3282
+ const weeks = Math.max(1, Math.min(106, Math.round(options.weeks ?? 53)));
3283
+ const firstDay = Number.isInteger(options.firstDay) && options.firstDay >= 0 && options.firstDay <= 6 ? options.firstDay : 1;
3284
+ const end = toDate(options.end ?? /* @__PURE__ */ new Date());
3285
+ const start = startOfWeek(addDays(end, -(weeks - 1) * 7), firstDay);
3286
+ const days = [];
3287
+ for (let date = start, i = 0; date <= end; date = addDays(date, 1), i++) {
3288
+ const key = dayKey(date);
3289
+ days.push({ key, date, value: values.get(key) ?? 0, week: Math.floor(i / 7), day: i % 7 });
3290
+ }
3291
+ const max = Math.max(0, ...days.map((each) => each.value));
3292
+ return { days, weeks: (days[days.length - 1]?.week ?? 0) + 1, firstDay, max, total: days.reduce((sum, each) => sum + each.value, 0) };
3293
+ };
3294
+ let model = modelOf();
3295
+ const levelOf = (value) => value <= 0 || !model.max ? 0 : Math.max(1, Math.ceil(value / model.max * levels()));
3296
+ const fillOf = (level) => level === 0 ? "var(--mk-heatmap-empty)" : `color-mix(in srgb, ${options.color ?? "var(--mk-heatmap-color)"} ${Math.round(22 + 78 * level / levels())}%, var(--mk-heatmap-empty))`;
3297
+ let cells = [];
3298
+ let builtFrom = "";
3299
+ let geometry = { cell: 12, gap: 3, width: 0, height: 0 };
3300
+ const longDate = () => safeFormat(locale(), { weekday: "short", day: "numeric", month: "long", year: "numeric" });
3301
+ const build = (animate) => {
3302
+ canvas.replaceChildren();
3303
+ const available = Math.max(0, (plot.clientWidth || root.clientWidth || 0) - LEFT);
3304
+ const gap = 3;
3305
+ const fit = available ? Math.floor(available / model.weeks - gap) : 12;
3306
+ const cell = Math.max(10, Math.min(14, fit));
3307
+ const width = LEFT + model.weeks * (cell + gap);
3308
+ const height = TOP + 7 * (cell + gap);
3309
+ geometry = { cell, gap, width, height };
3310
+ canvas.setAttribute("viewBox", `0 0 ${width} ${height}`);
3311
+ canvas.setAttribute("width", String(width));
3312
+ canvas.setAttribute("height", String(height));
3313
+ plot.style.setProperty("--mk-heatmap-width", `${width}px`);
3314
+ const months = svg2("g", { "data-mk-part": "months" });
3315
+ const monthName = safeFormat(locale(), { month: "short" });
3316
+ let last = -1;
3317
+ for (const each of model.days) {
3318
+ if (each.day !== 0) continue;
3319
+ const month = addDays(each.date, 6).getMonth();
3320
+ if (month === last) continue;
3321
+ last = month;
3322
+ if (each.week > model.weeks - 2 && model.weeks > 4) continue;
3323
+ const text = svg2("text", { x: LEFT + each.week * (cell + gap), y: 10 });
3324
+ text.textContent = capitalize(monthName.format(addDays(each.date, 6)).replace(".", ""));
3325
+ months.append(text);
3326
+ }
3327
+ const names = weekdayNames(locale(), model.firstDay, "short");
3328
+ const weekdays = svg2("g", { "data-mk-part": "weekdays" });
3329
+ [1, 3, 5].forEach((row) => {
3330
+ const text = svg2("text", { x: 0, y: round(TOP + row * (cell + gap) + cell / 2), "dominant-baseline": "middle" });
3331
+ text.textContent = capitalize(names[row].replace(".", "")).slice(0, 3);
3332
+ weekdays.append(text);
3333
+ });
3334
+ const grid = svg2("g", { "data-mk-part": "cells" });
3335
+ cells = model.days.map((each, i) => {
3336
+ const rect = svg2("rect", { x: LEFT + each.week * (cell + gap), y: TOP + each.day * (cell + gap), width: cell, height: cell, rx: 3, "data-mk-index": i, "data-mk-level": levelOf(each.value) });
3337
+ rect.style.fill = fillOf(levelOf(each.value));
3338
+ grid.append(rect);
3339
+ return rect;
3340
+ });
3341
+ canvas.append(months, weekdays, grid);
3342
+ builtFrom = model.days[0]?.key ?? "";
3343
+ scroller.scrollLeft = scroller.scrollWidth;
3344
+ if (animate && !reduced()) keep(gsap.fromTo(cells, { opacity: 0, scale: 0.3 }, { opacity: 1, scale: 1, duration: 0.35, ease: EASE.pop, clearProps: "opacity,transform", stagger: (i) => Math.min(0.7, (model.days[i].week + model.days[i].day) * 8e-3) }));
3345
+ };
3346
+ scope.add(() => plot.style.removeProperty("--mk-heatmap-width"));
3347
+ const paintScale = () => {
3348
+ scale.replaceChildren();
3349
+ scale.hidden = options.legend === false || !model.days.length;
3350
+ if (scale.hidden) return;
3351
+ const less = document.createElement("span");
3352
+ less.textContent = "Menos";
3353
+ scale.append(less);
3354
+ for (let level = 0; level <= levels(); level++) {
3355
+ const swatch = document.createElement("i");
3356
+ swatch.style.background = fillOf(level);
3357
+ swatch.addEventListener("pointerenter", () => {
3358
+ for (const cell of cells) cell.toggleAttribute("data-mk-dim", cell.getAttribute("data-mk-level") !== String(level));
3359
+ });
3360
+ swatch.addEventListener("pointerleave", () => {
3361
+ for (const cell of cells) cell.removeAttribute("data-mk-dim");
3362
+ });
3363
+ scale.append(swatch);
3364
+ }
3365
+ const more = document.createElement("span");
3366
+ more.textContent = "M\xE1s";
3367
+ scale.append(more);
3368
+ };
3369
+ const unit = () => options.unit ?? "actividades";
3370
+ const paintSelection = (announce) => {
3371
+ cells.forEach((cell2, i) => cell2.toggleAttribute("data-mk-on", i === selected));
3372
+ const each = selected === null ? null : model.days[selected];
3373
+ if (!each) {
3374
+ frame.tip(null);
3375
+ return;
3376
+ }
3377
+ const { cell, gap } = geometry;
3378
+ const x = LEFT + each.week * (cell + gap) + cell / 2 - scroller.scrollLeft;
3379
+ const y = TOP + each.day * (cell + gap);
3380
+ const title = capitalize(longDate().format(each.date));
3381
+ frame.tip({ x, y, title, rows: [{ name: unit(), color: fillOf(Math.max(1, levelOf(each.value))), value: format(each.value) }] });
3382
+ if (announce) frame.live.textContent = `${title}: ${format(each.value)} ${unit()}`;
3383
+ };
3384
+ const draw = (animate) => {
3385
+ const name = options.label ?? "Actividad diaria";
3386
+ canvas.setAttribute("aria-label", model.days.length ? `${name}. ${model.weeks} semanas, ${format(model.total)} ${unit()} en total. Usa las flechas para recorrer los d\xEDas.` : `${name}: ${options.emptyLabel ?? "sin datos"}`);
3387
+ const active = model.days.filter((each) => each.value > 0);
3388
+ frame.fillTable(`${name}: ${format(model.total)} ${unit()}`, ["D\xEDa", capitalize(unit())], active.map((each) => [capitalize(longDate().format(each.date)), format(each.value)]));
3389
+ frame.fillLegend([], false);
3390
+ paintScale();
3391
+ if (selected !== null && selected >= model.days.length) selected = null;
3392
+ const same = cells.length === model.days.length && cells.length > 0 && builtFrom === model.days[0].key;
3393
+ if (same && animate) {
3394
+ cells.forEach((cell, i) => {
3395
+ const level = levelOf(model.days[i].value);
3396
+ cell.setAttribute("data-mk-level", String(level));
3397
+ cell.style.fill = fillOf(level);
3398
+ });
3399
+ } else build(animate);
3400
+ paintSelection(false);
3401
+ };
3402
+ const select = (index, announce = false) => {
3403
+ if (scope.disposed) return;
3404
+ const next = index === null || !model.days.length ? null : Math.max(0, Math.min(model.days.length - 1, Math.round(index)));
3405
+ if (next === selected) return;
3406
+ selected = next;
3407
+ paintSelection(announce);
3408
+ const each = selected === null ? null : model.days[selected];
3409
+ options.onSelect?.(each?.key ?? null, each?.value ?? null);
3410
+ root.dispatchEvent(new CustomEvent("mk:select", { bubbles: true, detail: { date: each?.key ?? null, value: each?.value ?? null } }));
3411
+ };
3412
+ scope.on(canvas, "pointermove", (event) => {
3413
+ const cell = event.target.closest("[data-mk-index]");
3414
+ select(cell ? Number(cell.getAttribute("data-mk-index")) : null);
3415
+ });
3416
+ scope.on(canvas, "pointerleave", (event) => {
3417
+ if (event.pointerType === "mouse") select(null);
3418
+ });
3419
+ scope.on(canvas, "focus", () => {
3420
+ if (selected === null && model.days.length) select(model.days.length - 1, true);
3421
+ });
3422
+ scope.on(canvas, "blur", () => select(null));
3423
+ scope.on(canvas, "keydown", (event) => {
3424
+ const key = event.key;
3425
+ const at = selected ?? model.days.length - 1;
3426
+ const moves = { ArrowUp: at - 1, ArrowDown: at + 1, ArrowLeft: at - 7, ArrowRight: at + 7, PageUp: at - 28, PageDown: at + 28, Home: 0, End: model.days.length - 1, Escape: null };
3427
+ if (!(key in moves) || !model.days.length) return;
3428
+ event.preventDefault();
3429
+ select(moves[key], true);
3430
+ const cell = selected === null ? null : cells[selected];
3431
+ if (cell) {
3432
+ const x = Number(cell.getAttribute("x"));
3433
+ if (x - scroller.scrollLeft < LEFT || x - scroller.scrollLeft > scroller.clientWidth - geometry.cell * 2) scroller.scrollLeft = x - scroller.clientWidth / 2;
3434
+ }
3435
+ });
3436
+ onWidth(root, scope, () => {
3437
+ build(false);
3438
+ paintSelection(false);
3439
+ });
3440
+ draw(true);
3441
+ return {
3442
+ update(next) {
3443
+ if (scope.disposed) return;
3444
+ options = { ...options, ...next };
3445
+ model = modelOf();
3446
+ draw(true);
3447
+ },
3448
+ select(date) {
3449
+ const index = date === null ? -1 : model.days.findIndex((each) => each.key === date);
3450
+ select(index < 0 ? null : index);
3451
+ },
3452
+ get selected() {
3453
+ return selected === null ? null : model.days[selected]?.key ?? null;
3454
+ },
3455
+ destroy: () => scope.dispose()
3456
+ };
3457
+ }
3458
+
3459
+ // src/components/orbit-menu/engine.ts
3460
+ function createOrbitMenu(root, options = {}) {
3461
+ const toggle = root?.querySelector("[data-mk-orbit-toggle]") ?? null;
3462
+ const items2 = root ? [...root.querySelectorAll("[data-mk-orbit-item]")] : [];
3463
+ if (!root || !toggle || !items2.length) {
3464
+ if (root) warnOnce("orbit-menu: needs a [data-mk-orbit-toggle] and at least one [data-mk-orbit-item]");
3465
+ return inert({ open() {
3466
+ }, close() {
3467
+ }, toggle() {
3468
+ }, isOpen: false });
3469
+ }
3470
+ registerMotion();
3471
+ const scope = createScope();
3472
+ const radius = Number.isFinite(options.radius) ? Math.max(24, options.radius) : 96;
3473
+ const start = (Number.isFinite(options.start) ? options.start : 0) * Math.PI / 180;
3474
+ const angles = items2.map((_, i) => start + i * Math.PI * 2 / items2.length);
3475
+ const valueOf2 = (item) => item.dataset.mkValue ?? String(items2.indexOf(item));
3476
+ if (!toggle.children.length && !toggle.textContent?.trim()) {
3477
+ const burger = document.createElement("span");
3478
+ burger.dataset.mkOrbitBurger = "";
3479
+ burger.setAttribute("aria-hidden", "true");
3480
+ burger.append(document.createElement("i"), document.createElement("i"), document.createElement("i"));
3481
+ toggle.append(burger);
3482
+ scope.add(() => burger.remove());
3483
+ }
3484
+ scope.attr(toggle, "aria-expanded", "false");
3485
+ scope.attr(toggle, "aria-haspopup", "true");
3486
+ if (!toggle.getAttribute("aria-label") && !toggle.textContent?.trim()) scope.attr(toggle, "aria-label", "Men\xFA");
3487
+ for (const item of items2) {
3488
+ scope.attr(item, "inert", "");
3489
+ if (item instanceof HTMLButtonElement) scope.attr(item, "type", item.getAttribute("type") ?? "button");
3490
+ }
3491
+ scope.attr(root, "data-mk-ready", "");
3492
+ scope.attr(root, "data-mk-state", "closed");
3493
+ const states = items2.map((_, i) => ({ a: angles[i], r: 0 }));
3494
+ const apply = (i) => {
3495
+ const { a, r } = states[i];
3496
+ gsap.set(items2[i], { x: r * Math.sin(a), y: -r * Math.cos(a) });
3497
+ };
3498
+ let timeline = null;
3499
+ scope.add(() => {
3500
+ timeline?.kill();
3501
+ gsap.set(items2, { clearProps: "transform,opacity,filter,visibility" });
3502
+ });
3503
+ gsap.set(items2, { opacity: 0, scale: 0.3, visibility: "hidden" });
3504
+ let open = false;
3505
+ const set = (next, focus = false) => {
3506
+ if (next === open) return;
3507
+ open = next;
3508
+ toggle.setAttribute("aria-expanded", String(open));
3509
+ root.dataset.mkState = open ? "open" : "closed";
3510
+ for (const item of items2) item.toggleAttribute("inert", !open);
3511
+ timeline?.kill();
3512
+ if (reduced()) {
3513
+ items2.forEach((item, i) => {
3514
+ states[i] = { a: angles[i], r: open ? radius : 0 };
3515
+ apply(i);
3516
+ gsap.set(item, { opacity: open ? 1 : 0, scale: open ? 1 : 0.3, filter: "none", visibility: open ? "visible" : "hidden" });
3517
+ });
3518
+ } else if (open) {
3519
+ timeline = gsap.timeline();
3520
+ items2.forEach((item, i) => {
3521
+ const state = states[i];
3522
+ if (state.r < 1) state.a = angles[i] - 0.7;
3523
+ const at = i * 0.045;
3524
+ gsap.set(item, { visibility: "visible" });
3525
+ timeline.to(state, { r: radius, duration: 0.55, ease: EASE.swatch, onUpdate: () => apply(i) }, at);
3526
+ timeline.to(state, { a: angles[i], duration: 0.5, ease: "power3.out", onUpdate: () => apply(i) }, at);
3527
+ timeline.fromTo(item, { filter: "blur(8px)" }, { opacity: 1, scale: 1, filter: "blur(0px)", duration: 0.35, ease: "power2.out", clearProps: "filter" }, at);
3528
+ });
3529
+ } else {
3530
+ timeline = gsap.timeline();
3531
+ [...items2].reverse().forEach((item, k) => {
3532
+ const i = items2.length - 1 - k;
3533
+ const state = states[i];
3534
+ const at = k * 0.03;
3535
+ timeline.to(state, { r: 0, a: state.a + 0.6, duration: 0.3, ease: "power2.in", onUpdate: () => apply(i) }, at);
3536
+ timeline.to(item, { opacity: 0, scale: 0.3, filter: "blur(6px)", duration: 0.28, ease: "power2.in", onComplete: () => {
3537
+ gsap.set(item, { visibility: "hidden", clearProps: "filter" });
3538
+ } }, at);
3539
+ });
3540
+ }
3541
+ if (focus === "first") items2[0].focus();
3542
+ if (focus === "toggle") toggle.focus();
3543
+ options.onToggle?.(open);
3544
+ root.dispatchEvent(new CustomEvent("mk:toggle", { bubbles: true, detail: { open } }));
3545
+ };
3546
+ scope.on(toggle, "click", (event) => {
3547
+ press(toggle);
3548
+ set(!open, !open && event.detail === 0 ? "first" : false);
3549
+ });
3550
+ scope.on(root, "click", (event) => {
3551
+ const item = event.target.closest("[data-mk-orbit-item]");
3552
+ if (!item || !items2.includes(item) || !open) return;
3553
+ press(item);
3554
+ const value = valueOf2(item);
3555
+ options.onSelect?.(value);
3556
+ root.dispatchEvent(new CustomEvent("mk:select", { bubbles: true, detail: { value } }));
3557
+ if (options.closeOnSelect !== false) set(false, "toggle");
3558
+ });
3559
+ const sides = { ArrowUp: 0, ArrowRight: Math.PI / 2, ArrowDown: Math.PI, ArrowLeft: Math.PI * 3 / 2 };
3560
+ scope.on(root, "keydown", (event) => {
3561
+ const key = event.key;
3562
+ if (key === "Escape" && open) {
3563
+ event.preventDefault();
3564
+ set(false, "toggle");
3565
+ return;
3566
+ }
3567
+ if (!(key in sides)) return;
3568
+ if (!open) {
3569
+ if (document.activeElement !== toggle) return;
3570
+ event.preventDefault();
3571
+ set(true);
3572
+ } else event.preventDefault();
3573
+ const want = sides[key];
3574
+ const gap = (a) => {
3575
+ const d = Math.abs(((a - want) % (Math.PI * 2) + Math.PI * 3) % (Math.PI * 2) - Math.PI);
3576
+ return d;
3577
+ };
3578
+ let best = 0;
3579
+ angles.forEach((a, i) => {
3580
+ if (gap(a) < gap(angles[best])) best = i;
3581
+ });
3582
+ items2[best].focus();
3583
+ });
3584
+ scope.on(document, "pointerdown", (event) => {
3585
+ if (open && !root.contains(event.target)) set(false);
3586
+ });
3587
+ return {
3588
+ open: () => set(true),
3589
+ close: () => set(false),
3590
+ toggle: () => set(!open),
3591
+ get isOpen() {
3592
+ return open;
3593
+ },
3594
+ destroy: () => scope.dispose()
3595
+ };
3596
+ }
3597
+
3598
+ export {
3599
+ enterMessages,
3600
+ fitComposer,
3601
+ scrollToLatest,
3602
+ createThemePicker,
3603
+ ICON_PATHS,
3604
+ iconSvg,
3605
+ TOAST_LIMIT,
3606
+ getToasts,
3607
+ subscribeToasts,
3608
+ dismissToast,
3609
+ removeToast,
3610
+ clearToasts,
3611
+ toast,
3612
+ toastTimers,
3613
+ createToaster,
3614
+ PENDING_ID,
3615
+ capturePending,
3616
+ morphPendingToReply,
3617
+ chatMessage,
3618
+ createChat,
3619
+ NAV_STYLES,
3620
+ NAV_STYLE_ALIASES,
3621
+ toNavStyle,
3622
+ revealShell,
3623
+ createNav,
3624
+ createCursor,
3625
+ createIsland,
3626
+ PILL_ARROW,
3627
+ createPillButton,
3628
+ createToolbar,
3629
+ createBidirectionalSlider,
3630
+ createVoiceDots,
3631
+ createPieChart,
3632
+ createRadarChart,
3633
+ createRadialChart,
3634
+ createSankeyChart,
3635
+ createHeatmapChart,
3636
+ createOrbitMenu
3637
+ };
3638
+ //# sourceMappingURL=chunk-JXRS3OEA.js.map