@omerdlw/base-framework 1.0.1 → 1.0.3

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 (141) hide show
  1. package/dist/atoms/index.d.ts +0 -1
  2. package/dist/atoms/index.d.ts.map +1 -1
  3. package/dist/atoms/index.js +1 -64
  4. package/dist/atoms/index.js.map +1 -1
  5. package/dist/error/index.js +22 -25
  6. package/dist/error/index.js.map +1 -1
  7. package/dist/hooks/index.d.ts +2 -0
  8. package/dist/hooks/index.d.ts.map +1 -1
  9. package/dist/hooks/index.js +97 -4
  10. package/dist/hooks/index.js.map +1 -1
  11. package/dist/hooks/types.d.ts +11 -0
  12. package/dist/hooks/types.d.ts.map +1 -1
  13. package/dist/hooks/use-controllable-state.d.ts +1 -1
  14. package/dist/hooks/use-controllable-state.d.ts.map +1 -1
  15. package/dist/hooks/use-hotkey.d.ts +1 -1
  16. package/dist/hooks/use-hotkey.d.ts.map +1 -1
  17. package/dist/index.js +10811 -10696
  18. package/dist/index.js.map +1 -1
  19. package/dist/kernel/adapters.d.ts.map +1 -1
  20. package/dist/kernel/index.d.ts +1 -0
  21. package/dist/kernel/index.d.ts.map +1 -1
  22. package/dist/kernel/index.js +13 -84
  23. package/dist/kernel/index.js.map +1 -1
  24. package/dist/kernel/installed.d.ts.map +1 -1
  25. package/dist/kernel/page-controller.d.ts.map +1 -1
  26. package/dist/kernel/provider.d.ts.map +1 -1
  27. package/dist/modules/ambient/constants.d.ts +1 -1
  28. package/dist/modules/ambient/constants.d.ts.map +1 -1
  29. package/dist/modules/ambient/index.js +180 -326
  30. package/dist/modules/ambient/index.js.map +1 -1
  31. package/dist/modules/ambient/module.d.ts.map +1 -1
  32. package/dist/modules/background/constants.d.ts +4 -2
  33. package/dist/modules/background/constants.d.ts.map +1 -1
  34. package/dist/modules/background/context.d.ts +3 -0
  35. package/dist/modules/background/context.d.ts.map +1 -1
  36. package/dist/modules/background/hooks.d.ts +1 -1
  37. package/dist/modules/background/index.d.ts +2 -2
  38. package/dist/modules/background/index.d.ts.map +1 -1
  39. package/dist/modules/background/index.js +1172 -1270
  40. package/dist/modules/background/index.js.map +1 -1
  41. package/dist/modules/background/overlay.d.ts +1 -1
  42. package/dist/modules/background/overlay.d.ts.map +1 -1
  43. package/dist/modules/background/types.d.ts +1 -1
  44. package/dist/modules/background/types.d.ts.map +1 -1
  45. package/dist/modules/context-menu/constants.d.ts +1 -1
  46. package/dist/modules/context-menu/constants.d.ts.map +1 -1
  47. package/dist/modules/context-menu/context.d.ts.map +1 -1
  48. package/dist/modules/context-menu/hooks.d.ts.map +1 -1
  49. package/dist/modules/context-menu/index.js +135 -240
  50. package/dist/modules/context-menu/index.js.map +1 -1
  51. package/dist/modules/controls/constants.d.ts +1 -1
  52. package/dist/modules/controls/constants.d.ts.map +1 -1
  53. package/dist/modules/controls/hooks.d.ts +1 -1
  54. package/dist/modules/controls/index.js +31 -165
  55. package/dist/modules/controls/index.js.map +1 -1
  56. package/dist/modules/controls/types.d.ts +1 -1
  57. package/dist/modules/controls/types.d.ts.map +1 -1
  58. package/dist/modules/dock/cards.d.ts +1 -1
  59. package/dist/modules/dock/constants.d.ts +1 -1
  60. package/dist/modules/dock/constants.d.ts.map +1 -1
  61. package/dist/modules/dock/context.d.ts +10 -6
  62. package/dist/modules/dock/context.d.ts.map +1 -1
  63. package/dist/modules/dock/hooks.d.ts +4 -4
  64. package/dist/modules/dock/hooks.d.ts.map +1 -1
  65. package/dist/modules/dock/index.d.ts +1 -1
  66. package/dist/modules/dock/index.d.ts.map +1 -1
  67. package/dist/modules/dock/index.js +2063 -2083
  68. package/dist/modules/dock/index.js.map +1 -1
  69. package/dist/modules/dock/runtime/dock.d.ts.map +1 -1
  70. package/dist/modules/dock/types.d.ts +1 -1
  71. package/dist/modules/dock/types.d.ts.map +1 -1
  72. package/dist/modules/index.js +10193 -10350
  73. package/dist/modules/index.js.map +1 -1
  74. package/dist/modules/loading/constants.d.ts +4 -2
  75. package/dist/modules/loading/constants.d.ts.map +1 -1
  76. package/dist/modules/loading/context.d.ts +3 -0
  77. package/dist/modules/loading/context.d.ts.map +1 -1
  78. package/dist/modules/loading/hooks.d.ts +1 -1
  79. package/dist/modules/loading/hooks.d.ts.map +1 -1
  80. package/dist/modules/loading/index.d.ts +2 -2
  81. package/dist/modules/loading/index.d.ts.map +1 -1
  82. package/dist/modules/loading/index.js +121 -225
  83. package/dist/modules/loading/index.js.map +1 -1
  84. package/dist/modules/loading/types.d.ts +1 -1
  85. package/dist/modules/loading/types.d.ts.map +1 -1
  86. package/dist/modules/media/constants.d.ts +2 -1
  87. package/dist/modules/media/constants.d.ts.map +1 -1
  88. package/dist/modules/media/context.d.ts +3 -0
  89. package/dist/modules/media/context.d.ts.map +1 -1
  90. package/dist/modules/media/index.d.ts +2 -2
  91. package/dist/modules/media/index.d.ts.map +1 -1
  92. package/dist/modules/media/index.js +61 -218
  93. package/dist/modules/media/index.js.map +1 -1
  94. package/dist/modules/modal/constants.d.ts +1 -1
  95. package/dist/modules/modal/constants.d.ts.map +1 -1
  96. package/dist/modules/modal/context.d.ts.map +1 -1
  97. package/dist/modules/modal/hooks.d.ts +2 -2
  98. package/dist/modules/modal/index.js +31 -293
  99. package/dist/modules/modal/index.js.map +1 -1
  100. package/dist/modules/notification/constants.d.ts +4 -2
  101. package/dist/modules/notification/constants.d.ts.map +1 -1
  102. package/dist/modules/notification/context.d.ts +4 -0
  103. package/dist/modules/notification/context.d.ts.map +1 -1
  104. package/dist/modules/notification/hooks.d.ts +1 -1
  105. package/dist/modules/notification/index.d.ts +2 -3
  106. package/dist/modules/notification/index.d.ts.map +1 -1
  107. package/dist/modules/notification/index.js +65 -151
  108. package/dist/modules/notification/index.js.map +1 -1
  109. package/dist/modules/notification/module.d.ts.map +1 -1
  110. package/dist/provider.js +255 -257
  111. package/dist/provider.js.map +1 -1
  112. package/dist/theme.d.ts +1 -0
  113. package/dist/theme.d.ts.map +1 -1
  114. package/dist/theme.js +2 -2
  115. package/dist/theme.js.map +1 -1
  116. package/dist/utils/dom.d.ts +2 -0
  117. package/dist/utils/dom.d.ts.map +1 -1
  118. package/dist/utils/index.d.ts +1 -1
  119. package/dist/utils/index.d.ts.map +1 -1
  120. package/dist/utils/index.js +28 -19
  121. package/dist/utils/index.js.map +1 -1
  122. package/docs/modules/loading.md +1 -1
  123. package/package.json +48 -2
  124. package/dist/hooks/use-async-action.d.ts +0 -3
  125. package/dist/hooks/use-async-action.d.ts.map +0 -1
  126. package/dist/hooks/use-debounce.d.ts +0 -2
  127. package/dist/hooks/use-debounce.d.ts.map +0 -1
  128. package/dist/hooks/use-intersection-observer.d.ts +0 -4
  129. package/dist/hooks/use-intersection-observer.d.ts.map +0 -1
  130. package/dist/hooks/use-media-query.d.ts +0 -2
  131. package/dist/hooks/use-media-query.d.ts.map +0 -1
  132. package/dist/hooks/use-mounted.d.ts +0 -2
  133. package/dist/hooks/use-mounted.d.ts.map +0 -1
  134. package/dist/hooks/use-server-action.d.ts +0 -4
  135. package/dist/hooks/use-server-action.d.ts.map +0 -1
  136. package/dist/hooks/use-storage-state.d.ts +0 -5
  137. package/dist/hooks/use-storage-state.d.ts.map +0 -1
  138. package/dist/modules/theme.d.ts +0 -3
  139. package/dist/modules/theme.d.ts.map +0 -1
  140. package/dist/utils/json.d.ts +0 -3
  141. package/dist/utils/json.d.ts.map +0 -1
@@ -1,10 +1,9 @@
1
1
  "use client";
2
- import { memo, createContext, useState, useRef, useEffect, useMemo, useCallback, useSyncExternalStore, use, useInsertionEffect, useLayoutEffect, useId, useContext, isValidElement, cloneElement, Children } from 'react';
2
+ import { memo, createContext, useState, useRef, useEffect, useMemo, useCallback, useInsertionEffect, useContext, useSyncExternalStore, useLayoutEffect, use, useId, isValidElement, cloneElement, Children } from 'react';
3
3
  import { clsx } from 'clsx';
4
4
  import { twMerge } from 'tailwind-merge';
5
5
  import { Icon as Icon$1 } from '@iconify-icon/react';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
- import '@radix-ui/react-tooltip';
8
7
  import { AnimatePresence, motion } from 'motion/react';
9
8
  import { usePathname } from 'next/navigation';
10
9
 
@@ -12,9 +11,6 @@ function cn(...inputs) {
12
11
  return twMerge(clsx(inputs));
13
12
  }
14
13
 
15
- // src/utils/dom.ts
16
- var isBrowser = typeof window !== "undefined";
17
-
18
14
  // src/utils/string.ts
19
15
  function trimToNull(value) {
20
16
  if (value === null || value === void 0) return null;
@@ -22,6 +18,9 @@ function trimToNull(value) {
22
18
  return trimmed.length > 0 ? trimmed : null;
23
19
  }
24
20
 
21
+ // src/utils/dom.ts
22
+ var isBrowser = typeof window !== "undefined";
23
+
25
24
  // src/utils/object.ts
26
25
  function isObject(value) {
27
26
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -185,6 +184,58 @@ Object.freeze({
185
184
  duration: DURATION_TOKENS.INSTANT,
186
185
  ease: "linear"
187
186
  });
187
+
188
+ // src/kernel/constants.ts
189
+ var DEFAULT_SOURCE = "dynamic";
190
+ var REGISTRY_SOURCES = Object.freeze({
191
+ STATIC: "static",
192
+ DYNAMIC: "dynamic",
193
+ USER: "user"
194
+ });
195
+ var REGISTRY_LIFECYCLES = Object.freeze({
196
+ IMMEDIATE: "immediate",
197
+ GRACEFUL: "graceful",
198
+ PERSISTENT: "persistent",
199
+ ROUTE: "route"
200
+ });
201
+ var REGISTRY_VALIDATION_MODES = Object.freeze({
202
+ WARN: "warn",
203
+ STRICT: "strict"
204
+ });
205
+ var REGISTRY_METADATA_KEYS = Object.freeze([
206
+ "cleanup",
207
+ "cleanupDelayMs",
208
+ "instanceId",
209
+ "lifecycle",
210
+ "priority",
211
+ "source",
212
+ "scope",
213
+ "validation"
214
+ ]);
215
+ Object.freeze({
216
+ [REGISTRY_SOURCES.STATIC]: 100,
217
+ [REGISTRY_SOURCES.DYNAMIC]: 200,
218
+ [REGISTRY_SOURCES.USER]: 300
219
+ });
220
+ Object.freeze({
221
+ [REGISTRY_SOURCES.STATIC]: 10,
222
+ [REGISTRY_SOURCES.DYNAMIC]: 20,
223
+ [REGISTRY_SOURCES.USER]: 30
224
+ });
225
+ var REGISTRY_LIFECYCLE_VALUES = new Set(
226
+ Object.values(REGISTRY_LIFECYCLES)
227
+ );
228
+ var REGISTRY_METADATA_KEY_SET = new Set(
229
+ REGISTRY_METADATA_KEYS
230
+ );
231
+ var REGISTRY_SCOPE_KINDS = Object.freeze({
232
+ APP: "app",
233
+ SESSION: "session",
234
+ ROUTE: "route",
235
+ INSTANCE: "instance",
236
+ WORKSPACE: "workspace"
237
+ });
238
+ REGISTRY_SCOPE_KINDS.APP;
188
239
  function getOrCreateGlobalContext(name, defaultValue) {
189
240
  const sym = /* @__PURE__ */ Symbol.for(`@omerdlw/base-framework/context/${name}`);
190
241
  const store = globalThis;
@@ -194,1389 +245,1225 @@ function getOrCreateGlobalContext(name, defaultValue) {
194
245
  return store[sym];
195
246
  }
196
247
 
197
- // src/theme.tsx
198
- function defineThemeSpec(id) {
199
- return Object.freeze({ id });
248
+ // src/kernel/utils.ts
249
+ function parseFiniteNumber(value) {
250
+ const parsed = Number(value);
251
+ return Number.isFinite(parsed) ? parsed : null;
200
252
  }
201
- var EMPTY = /* @__PURE__ */ new Map();
202
- var ThemeContext = getOrCreateGlobalContext("ThemeContext", EMPTY);
203
- function useTheme(spec) {
204
- const config = useContext(ThemeContext).get(spec.id);
205
- if (!config) {
206
- throw new Error(
207
- `Theme "${spec.id}" is missing. Add its theme file to src/config/ and list it in src/config/index.ts.`
208
- );
209
- }
210
- return {
211
- slots: config.slots,
212
- styles: config.styles ?? {}
213
- };
253
+ function getValidString(value, fallback = null) {
254
+ return trimToNull(value) ?? fallback;
214
255
  }
215
256
 
216
- // src/modules/background/constants.ts
217
- var DEFAULT_BACKGROUND = Object.freeze({
218
- animation: null,
219
- className: "",
220
- fadeEdges: null,
221
- fit: null,
222
- image: null,
223
- imageStyle: {},
224
- isPlaying: false,
225
- noiseStyle: {},
226
- overlay: false,
227
- overlayColor: "var(--black)",
228
- overlayOpacity: 0,
229
- position: "center",
230
- video: null,
231
- videoClassName: "",
232
- videoElement: null,
233
- videoOptions: {
234
- autoplay: true,
235
- className: "",
236
- corp: 0,
237
- loop: false,
238
- muted: true,
239
- playbackRate: 1,
240
- width: null
241
- },
242
- videoStyle: {},
243
- width: null
244
- });
245
- var objectFit = (value) => ({
246
- objectFit: value
247
- });
248
- var objectPosition = (value) => ({
249
- objectPosition: value
250
- });
251
- var BG_TOKEN_TO_OBJECT_STYLE = Object.freeze({
252
- "bg-bottom": objectPosition("bottom"),
253
- "bg-center": objectPosition("center"),
254
- "bg-contain": objectFit("contain"),
255
- "bg-cover": objectFit("cover"),
256
- "bg-fill": objectFit("fill"),
257
- "bg-left": objectPosition("left"),
258
- "bg-left-bottom": objectPosition("left bottom"),
259
- "bg-left-top": objectPosition("left top"),
260
- "bg-none": objectFit("none"),
261
- "bg-right": objectPosition("right"),
262
- "bg-right-bottom": objectPosition("right bottom"),
263
- "bg-right-top": objectPosition("right top"),
264
- "bg-scale-down": objectFit("scale-down"),
265
- "bg-top": objectPosition("top")
266
- });
267
- var OBJECT_FITS = Object.freeze([
268
- "contain",
269
- "cover",
270
- "fill",
271
- "none",
272
- "scale-down"
273
- ]);
274
- var DEFAULT_COLOR = "var(--black, #000)";
275
- var backgroundTheme = defineThemeSpec("background");
276
- var BACKGROUND_REGISTRY_KEY = "page-background";
277
- var BACKGROUND_MEDIA_ID = "background";
278
-
279
- // src/modules/background/youtube/parse.ts
280
- var YOUTUBE_ID_PATTERN = /^[a-zA-Z0-9_-]{11}$/;
281
- function parseYouTubeTimeParam(value) {
282
- if (value === void 0 || value === null || value === "") return 0;
283
- if (typeof value === "number") {
284
- return Number.isFinite(value) && value >= 0 ? value : 0;
257
+ // src/kernel/schema.ts
258
+ function resolveLifecycle(metadata, fallback) {
259
+ const lifecycle = metadata?.lifecycle ?? metadata?.cleanup;
260
+ return lifecycle && REGISTRY_LIFECYCLE_VALUES.has(lifecycle) ? lifecycle : fallback;
261
+ }
262
+ function validateRegistryMetadata(metadata) {
263
+ if (metadata === void 0 || metadata === null)
264
+ return { issues: [], valid: true };
265
+ if (!isObject(metadata))
266
+ return { issues: ["registry metadata must be an object"], valid: false };
267
+ const issues = [];
268
+ Object.keys(metadata).forEach((key) => {
269
+ if (!REGISTRY_METADATA_KEY_SET.has(key))
270
+ issues.push(`unknown registry metadata field: ${key}`);
271
+ });
272
+ if (metadata.source !== void 0 && !getValidString(metadata.source))
273
+ issues.push("source must be a non-empty string");
274
+ if (metadata.instanceId !== void 0 && !getValidString(metadata.instanceId))
275
+ issues.push("instanceId must be a non-empty string");
276
+ if (metadata.scope !== void 0 && !getValidString(metadata.scope))
277
+ issues.push("scope must be a non-empty string");
278
+ if (metadata.priority !== void 0 && parseFiniteNumber(metadata.priority) === null)
279
+ issues.push("priority must be a finite number");
280
+ if (metadata.cleanupDelayMs !== void 0) {
281
+ const delay = parseFiniteNumber(metadata.cleanupDelayMs);
282
+ if (delay === null || delay < 0)
283
+ issues.push("cleanupDelayMs must be a non-negative finite number");
285
284
  }
286
- const trimmed = String(value).trim().toLowerCase();
287
- if (!trimmed) return 0;
288
- if (/^\d+(\.\d+)?$/.test(trimmed)) {
289
- return Math.max(0, parseFloat(trimmed));
285
+ const lifecycle = metadata.lifecycle ?? metadata.cleanup;
286
+ if (lifecycle !== void 0 && !REGISTRY_LIFECYCLE_VALUES.has(String(lifecycle))) {
287
+ issues.push(
288
+ `lifecycle must be one of: ${[...REGISTRY_LIFECYCLE_VALUES].join(", ")}`
289
+ );
290
290
  }
291
- const hmsMatch = trimmed.match(
292
- /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+(?:\.\d+)?)s?)?$/
293
- );
294
- if (hmsMatch && (hmsMatch[1] || hmsMatch[2] || hmsMatch[3])) {
295
- const hours = parseInt(hmsMatch[1] || "0", 10);
296
- const minutes = parseInt(hmsMatch[2] || "0", 10);
297
- const seconds = parseFloat(hmsMatch[3] || "0");
298
- return Math.max(0, hours * 3600 + minutes * 60 + seconds);
291
+ if (metadata.validation !== void 0 && !Object.values(REGISTRY_VALIDATION_MODES).includes(
292
+ metadata.validation
293
+ )) {
294
+ issues.push(
295
+ `validation must be one of: ${Object.values(REGISTRY_VALIDATION_MODES).join(", ")}`
296
+ );
299
297
  }
300
- return 0;
298
+ return { issues, valid: issues.length === 0 };
301
299
  }
302
- var YOUTUBE_HOST_PATH = /^(?:www\.|m\.|music\.)?(?:youtube\.com|youtu\.be|youtube-nocookie\.com)\//i;
303
- function toUrl(input, { youtubeOnly = false } = {}) {
304
- let candidate = null;
305
- if (/^https?:\/\//.test(input)) candidate = input;
306
- else if (input.startsWith("//")) candidate = `https:${input}`;
307
- else if (!youtubeOnly || YOUTUBE_HOST_PATH.test(input)) {
308
- candidate = `https://${input}`;
309
- }
310
- try {
311
- return candidate ? new URL(candidate) : null;
312
- } catch {
313
- return null;
300
+ function normalizeRegistryMetadata(metadata, {
301
+ defaultCleanupDelayMs = null,
302
+ defaultLifecycle = null,
303
+ defaultSource = DEFAULT_SOURCE
304
+ } = {}) {
305
+ const registryMeta = isObject(metadata) ? metadata : {};
306
+ const normalizedDefaultDelay = parseFiniteNumber(defaultCleanupDelayMs);
307
+ const fallbackCleanupDelayMs = normalizedDefaultDelay !== null && normalizedDefaultDelay >= 0 ? normalizedDefaultDelay : null;
308
+ const fallbackLifecycle = defaultLifecycle && REGISTRY_LIFECYCLE_VALUES.has(defaultLifecycle) ? defaultLifecycle : null;
309
+ const source = getValidString(registryMeta.source) || getValidString(defaultSource, DEFAULT_SOURCE) || DEFAULT_SOURCE;
310
+ const scope = getValidString(registryMeta.scope);
311
+ const priority = parseFiniteNumber(registryMeta.priority);
312
+ const lifecycle = resolveLifecycle(registryMeta, fallbackLifecycle);
313
+ const validation = isValidationMode(registryMeta.validation) ? registryMeta.validation : null;
314
+ const cleanupDelayCandidate = parseFiniteNumber(registryMeta.cleanupDelayMs);
315
+ let cleanupDelayMs = cleanupDelayCandidate !== null && cleanupDelayCandidate >= 0 ? cleanupDelayCandidate : fallbackCleanupDelayMs;
316
+ if (lifecycle === REGISTRY_LIFECYCLES.IMMEDIATE) {
317
+ cleanupDelayMs = 0;
318
+ } else if ((lifecycle === REGISTRY_LIFECYCLES.GRACEFUL || lifecycle === REGISTRY_LIFECYCLES.ROUTE) && cleanupDelayMs === null) {
319
+ cleanupDelayMs = fallbackCleanupDelayMs;
314
320
  }
321
+ return {
322
+ cleanupDelayMs,
323
+ lifecycle,
324
+ priority,
325
+ registerOptions: {
326
+ ...priority !== null && { priority },
327
+ ...validation === REGISTRY_VALIDATION_MODES.STRICT && { validation },
328
+ ...scope && { scope }
329
+ },
330
+ source,
331
+ ...scope && { scope },
332
+ ...validation && { validation }
333
+ };
315
334
  }
316
- function getIdFromUrl(url) {
317
- const host = url.hostname.replace(/^(www\.|m\.|music\.)/i, "").toLowerCase();
318
- const segments = url.pathname.split("/").filter(Boolean);
319
- if (host === "youtu.be") {
320
- return YOUTUBE_ID_PATTERN.test(segments[0] ?? "") ? segments[0] : null;
321
- }
322
- if (host !== "youtube.com" && host !== "youtube-nocookie.com" && !host.endsWith(".youtube.com")) {
323
- return null;
324
- }
325
- const fromQuery = url.searchParams.get("v") || url.searchParams.get("vi");
326
- if (fromQuery && YOUTUBE_ID_PATTERN.test(fromQuery)) return fromQuery;
327
- const isVideoPath = segments.length >= 2 && ["embed", "shorts", "live", "v", "e", "watch"].includes(
328
- segments[0].toLowerCase()
329
- );
330
- return isVideoPath && YOUTUBE_ID_PATTERN.test(segments[1]) ? segments[1] : null;
331
- }
332
- function extractYouTubeVideoId(input, options = {}) {
333
- const trimmed = typeof input === "string" ? input.trim() : "";
334
- if (!trimmed) return null;
335
- if (options.allowBareId && YOUTUBE_ID_PATTERN.test(trimmed)) return trimmed;
336
- const prefixed = trimmed.match(/^(?:youtube|yt):([a-zA-Z0-9_-]{11})$/i);
337
- if (prefixed) return prefixed[1];
338
- const url = toUrl(trimmed, { youtubeOnly: true });
339
- return url ? getIdFromUrl(url) : null;
335
+ function isValidationMode(value) {
336
+ return Object.values(REGISTRY_VALIDATION_MODES).includes(value);
340
337
  }
341
- function isYouTubeUrl(input) {
342
- return Boolean(extractYouTubeVideoId(input));
338
+ function pickRegistryMetadata(options) {
339
+ if (!isObject(options)) return {};
340
+ return Object.fromEntries(
341
+ REGISTRY_METADATA_KEYS.filter((key) => options[key] !== void 0).map(
342
+ (key) => [key, options[key]]
343
+ )
344
+ );
343
345
  }
344
- function isDirectVideoUrl(input) {
345
- if (!input || typeof input !== "string") return false;
346
- const trimmed = input.trim();
347
- if (!trimmed) return false;
348
- if (isYouTubeUrl(trimmed)) return true;
349
- if (trimmed.startsWith("/api/background/youtube") || trimmed.startsWith("blob:")) {
350
- return true;
346
+ function useRequiredContext(context, hookName, providerName) {
347
+ const value = use(context);
348
+ if (value === null) {
349
+ throw new Error(`${hookName} must be used within ${providerName}`);
351
350
  }
352
- const cleanPath = trimmed.split("?")[0].split("#")[0].toLowerCase();
353
- return /\.(mp4|webm|mov|m4v|ogg|ogv|m3u8)$/i.test(cleanPath);
351
+ return value;
354
352
  }
355
- function parseYouTubeUrlConfig(input, options = {}) {
356
- const videoId = extractYouTubeVideoId(input, options);
357
- if (!videoId || !input) return null;
358
- const params = toUrl(input.trim())?.searchParams;
359
- return {
360
- endTime: parseYouTubeTimeParam(params?.get("end")),
361
- listId: params?.get("list") || null,
362
- rawUrl: input,
363
- startTime: parseYouTubeTimeParam(params?.get("t") || params?.get("start")),
364
- videoId
365
- };
353
+ function useStore(store, selector = (state) => state, isEqual = Object.is) {
354
+ const cacheRef = useRef({
355
+ isEqual: null,
356
+ selected: void 0,
357
+ selector: null,
358
+ snapshot: null
359
+ });
360
+ const getSelectedSnapshot = useCallback(() => {
361
+ const snapshot = store.getSnapshot();
362
+ const cache = cacheRef.current;
363
+ if (cache.snapshot === snapshot && cache.selector === selector && cache.isEqual === isEqual) {
364
+ return cache.selected;
365
+ }
366
+ const selected = selector(snapshot);
367
+ if (cache.snapshot !== null && cache.isEqual === isEqual && isEqual(cache.selected, selected)) {
368
+ cache.snapshot = snapshot;
369
+ cache.selector = selector;
370
+ return cache.selected;
371
+ }
372
+ cacheRef.current = {
373
+ isEqual,
374
+ selected,
375
+ selector,
376
+ snapshot
377
+ };
378
+ return selected;
379
+ }, [isEqual, selector, store]);
380
+ return useSyncExternalStore(
381
+ store.subscribe,
382
+ getSelectedSnapshot,
383
+ getSelectedSnapshot
384
+ );
366
385
  }
367
- function getYouTubeStreamUrl(videoId, options = {}) {
368
- const params = new URLSearchParams();
369
- params.set("id", videoId);
370
- params.set("stream", options.stream || "video");
371
- if (options.quality) params.set("quality", options.quality);
372
- if (options.codec) params.set("codec", options.codec);
373
- return `/api/background/youtube?${params.toString()}`;
386
+ var useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect;
387
+ var RegistryContext = getOrCreateGlobalContext(
388
+ "RegistryContext",
389
+ null
390
+ );
391
+ function useRegistryActions() {
392
+ return useRequiredContext(
393
+ RegistryContext,
394
+ "useRegistryActions",
395
+ "RegistryProvider"
396
+ ).actions;
374
397
  }
375
- function getYouTubeThumbnailUrl(videoId, proxied = true) {
376
- if (proxied) {
377
- return `/api/background/youtube?id=${encodeURIComponent(videoId)}&stream=thumbnail`;
378
- }
379
- return `https://i.ytimg.com/vi/${encodeURIComponent(videoId)}/maxresdefault.jpg`;
398
+ function useRegistrySubscription() {
399
+ return useRequiredContext(
400
+ RegistryContext,
401
+ "useRegistrySubscription",
402
+ "RegistryProvider"
403
+ ).subscription;
380
404
  }
381
- function createYouTubeVideoElementProxy(controller) {
382
- const proxyVideo = document.createElement("video");
383
- Object.defineProperties(proxyVideo, {
384
- currentTime: {
385
- configurable: true,
386
- get: () => controller.getCurrentTime(),
387
- set: (value) => {
388
- controller.seekTo(Number(value) || 0);
389
- proxyVideo.dispatchEvent(new Event("timeupdate"));
390
- }
391
- },
392
- duration: {
393
- configurable: true,
394
- get: () => controller.getDuration()
395
- },
396
- loop: {
397
- configurable: true,
398
- get: () => controller.getLoop(),
399
- set: (value) => {
400
- controller.setLoop(Boolean(value));
401
- }
402
- },
403
- muted: {
404
- configurable: true,
405
- get: () => controller.getMuted(),
406
- set: (value) => {
407
- controller.setMuted(Boolean(value));
408
- proxyVideo.dispatchEvent(new Event("volumechange"));
409
- }
410
- },
411
- pause: {
412
- configurable: true,
413
- value: () => {
414
- controller.pause();
415
- proxyVideo.dispatchEvent(new Event("pause"));
416
- }
417
- },
418
- paused: {
419
- configurable: true,
420
- get: () => controller.getPaused()
421
- },
422
- play: {
423
- configurable: true,
424
- value: () => controller.play().then(() => {
425
- proxyVideo.dispatchEvent(new Event("play"));
426
- proxyVideo.dispatchEvent(new Event("playing"));
427
- })
428
- },
429
- playbackRate: {
430
- configurable: true,
431
- get: () => controller.getPlaybackRate(),
432
- set: (value) => {
433
- controller.setPlaybackRate(Number(value) || 1);
434
- proxyVideo.dispatchEvent(new Event("ratechange"));
435
- }
436
- },
437
- volume: {
438
- configurable: true,
439
- get: () => controller.getVolume(),
440
- set: (value) => {
441
- const clamped = Math.max(0, Math.min(1, Number(value) || 0));
442
- controller.setVolume(clamped);
443
- proxyVideo.dispatchEvent(new Event("volumechange"));
444
- }
445
- }
446
- });
447
- return proxyVideo;
405
+ function useRegistryValue(type, key) {
406
+ const { getSnapshot, subscribe } = useRegistrySubscription();
407
+ const subscribeToKey = useCallback(
408
+ (listener) => subscribe(type, key, listener),
409
+ [key, subscribe, type]
410
+ );
411
+ const getValue = useCallback(
412
+ () => getSnapshot(type, key),
413
+ [getSnapshot, key, type]
414
+ );
415
+ return useSyncExternalStore(subscribeToKey, getValue, getValue);
448
416
  }
449
-
450
- // src/modules/background/utils.ts
451
- var WIDTH_CLASS = /^(w-|max-w-|min-w-)/;
452
- function resolveVideoClassNames(...inputs) {
453
- const tokens = inputs.flatMap(
454
- (input) => typeof input === "string" ? input.replace(/,/g, " ").split(/\s+/) : []
455
- ).filter(Boolean);
456
- return {
457
- objectStyle: Object.assign(
458
- {},
459
- ...tokens.map((token) => BG_TOKEN_TO_OBJECT_STYLE[token])
460
- ),
461
- other: tokens.filter((token) => !WIDTH_CLASS.test(token)).join(" "),
462
- width: tokens.filter((token) => WIDTH_CLASS.test(token)).join(" ")
463
- };
417
+ var EMPTY_MODULES = Object.freeze([]);
418
+ var ModuleHostContext = getOrCreateGlobalContext("ModuleHostContext", null);
419
+ function selectModuleSlice(coreModule, config) {
420
+ const slice = coreModule.page?.select ? coreModule.page.select(config) : config[coreModule.id];
421
+ return slice ?? null;
464
422
  }
465
- function resolveVideoOptions(options = {}) {
423
+ function createModulePayload(modules, config, metadata = {}) {
424
+ const payload = {};
425
+ let hasSlices = false;
426
+ for (const coreModule of modules) {
427
+ if (!coreModule.page?.entries) continue;
428
+ const slice = selectModuleSlice(coreModule, config);
429
+ if (slice === null) continue;
430
+ payload[coreModule.id] = slice;
431
+ hasSlices = true;
432
+ }
433
+ if (!hasSlices) return null;
466
434
  return {
467
- codec: options.codec || "auto",
468
- corp: options.corp ?? 0,
469
- forceIframe: Boolean(options.forceIframe),
470
- isLoop: options.loop ?? false,
471
- isMuted: options.muted ?? true,
472
- playbackRate: options.playbackRate ?? 1,
473
- quality: options.quality || "1080p",
474
- shouldAutoPlay: options.autoplay ?? true,
475
- showPoster: Boolean(options.showPoster),
476
- showSpinner: options.showSpinner !== false
435
+ ...payload,
436
+ registry: {
437
+ ...metadata,
438
+ ...isObject(config.registry) ? config.registry : {}
439
+ }
477
440
  };
478
441
  }
479
- function normalizeBackgroundInput(patch = {}) {
480
- if (!patch) return {};
481
- if (typeof patch === "string") {
482
- const trimmed = patch.trim();
483
- if (isYouTubeUrl(trimmed) || isDirectVideoUrl(trimmed)) {
484
- return { image: null, video: trimmed };
485
- }
486
- return { image: trimmed };
487
- }
488
- const nextPatch = { ...patch };
489
- if (typeof nextPatch.image === "string" && isYouTubeUrl(nextPatch.image) && !nextPatch.video) {
490
- nextPatch.video = nextPatch.image;
491
- nextPatch.image = null;
492
- }
493
- return nextPatch;
442
+ function useInstalledModules() {
443
+ return use(ModuleHostContext)?.modules ?? EMPTY_MODULES;
494
444
  }
495
- function mergeBackgroundState(baseState, patch = {}) {
496
- const resolvedPatch = normalizeBackgroundInput(patch);
497
- const nextVideoOptions = {
498
- ...baseState.videoOptions,
499
- ...resolvedPatch.videoOptions || {}
500
- };
501
- const nextVideo = resolvedPatch.video !== void 0 ? resolvedPatch.video : baseState.video;
502
- const shouldAutoPlay = nextVideoOptions.autoplay !== false;
503
- const resolvedIsPlaying = resolvedPatch.isPlaying !== void 0 ? resolvedPatch.isPlaying : resolvedPatch.video !== void 0 && Boolean(nextVideo) ? shouldAutoPlay : baseState.isPlaying;
504
- return {
505
- ...baseState,
506
- ...resolvedPatch,
507
- isPlaying: resolvedIsPlaying,
508
- animation: resolvedPatch.animation !== void 0 ? resolvedPatch.animation ? {
509
- ...baseState.animation || {},
510
- ...resolvedPatch.animation
511
- } : resolvedPatch.animation : baseState.animation,
512
- imageStyle: {
513
- ...baseState.imageStyle,
514
- ...resolvedPatch.imageStyle || {}
515
- },
516
- noiseStyle: {
517
- ...baseState.noiseStyle,
518
- ...resolvedPatch.noiseStyle || {}
519
- },
520
- videoOptions: nextVideoOptions,
521
- videoStyle: {
522
- ...baseState.videoStyle,
523
- ...resolvedPatch.videoStyle || {}
524
- }
525
- };
445
+
446
+ // src/kernel/handlers.ts
447
+ function isPersistentLifecycle(lifecycle) {
448
+ return lifecycle === REGISTRY_LIFECYCLES.PERSISTENT;
526
449
  }
527
- function selectPageBackground(background) {
528
- return background ? normalizeBackgroundInput(background) : null;
450
+ function runRegistrationBatch(context, executor) {
451
+ return typeof context.batch === "function" ? context.batch(executor) : executor(context);
529
452
  }
530
- var generateBaseGradient = (direction, color) => {
531
- const to = direction === "left" ? "to right" : "to left";
532
- return `linear-gradient(${to}, ${color} 0%, color-mix(in srgb, ${color} 85%, transparent) 15%, color-mix(in srgb, ${color} 50%, transparent) 45%, color-mix(in srgb, ${color} 18%, transparent) 75%, transparent 100%)`;
533
- };
534
- var generateEdgeGradient = (direction, color) => {
535
- const to = direction === "left" ? "to right" : "to left";
536
- return `linear-gradient(${to}, color-mix(in srgb, ${color} 95%, transparent) 0%, color-mix(in srgb, ${color} 70%, transparent) 25%, color-mix(in srgb, ${color} 35%, transparent) 55%, color-mix(in srgb, ${color} 10%, transparent) 80%, transparent 100%)`;
537
- };
538
- function getVisualStyle(currentStyle = {}) {
539
- const {
540
- className: _className,
541
- leftGradient = 0,
542
- rightGradient = 0,
543
- ...baseStyle
544
- } = currentStyle;
545
- return {
546
- baseStyle,
547
- leftGradient: Number(leftGradient) || 0,
548
- rightGradient: Number(rightGradient) || 0
549
- };
453
+ function createEntryCleanupKey(type, key, source, instanceId, scope) {
454
+ return JSON.stringify([
455
+ "entry",
456
+ type,
457
+ key,
458
+ scope ?? null,
459
+ source,
460
+ instanceId
461
+ ]);
550
462
  }
551
- function resolveGradientSettings({
552
- fadeEdges = null,
553
- hasWidth = false,
554
- leftGradient = 0,
555
- rightGradient = 0
556
- }) {
557
- let leftPercent = 0;
558
- let rightPercent = 0;
559
- if (leftGradient > 0)
560
- leftPercent = Math.min(48, Math.max(12, leftGradient * 7.5));
561
- else if (hasWidth && fadeEdges !== false) leftPercent = 20;
562
- if (rightGradient > 0)
563
- rightPercent = Math.min(48, Math.max(12, rightGradient * 7.5));
564
- else if (hasWidth && fadeEdges !== false) rightPercent = 20;
565
- if (typeof fadeEdges === "number") {
566
- leftPercent = fadeEdges;
567
- rightPercent = fadeEdges;
568
- } else if (typeof fadeEdges === "string") {
569
- const parsed = parseFloat(fadeEdges);
570
- if (!Number.isNaN(parsed)) {
571
- leftPercent = parsed;
572
- rightPercent = parsed;
573
- }
574
- } else if (typeof fadeEdges === "object" && fadeEdges !== null) {
575
- const edgeObj = fadeEdges;
576
- if (edgeObj.left !== void 0)
577
- leftPercent = parseFloat(String(edgeObj.left)) || 0;
578
- if (edgeObj.right !== void 0)
579
- rightPercent = parseFloat(String(edgeObj.right)) || 0;
580
- } else if (fadeEdges === false) {
581
- leftPercent = 0;
582
- rightPercent = 0;
583
- }
584
- const leftOpacity = leftGradient > 0 ? Math.min(1, leftGradient * 0.22) : 0;
585
- const rightOpacity = rightGradient > 0 ? Math.min(1, rightGradient * 0.22) : 0;
586
- return {
587
- enabled: leftPercent > 0 || rightPercent > 0,
588
- leftOpacity,
589
- leftPercent,
590
- rightOpacity,
591
- rightPercent
592
- };
463
+ function getCleanupScope(context) {
464
+ return context?.cleanupScope || /* @__PURE__ */ new Map();
593
465
  }
594
- function generateSmoothstepStops(percent, direction = "in", color = "var(--black, #000)") {
595
- const steps = [
596
- { s: 0, t: 0 },
597
- { s: 0.06, t: 0.15 },
598
- { s: 0.22, t: 0.35 },
599
- { s: 0.47, t: 0.55 },
600
- { s: 0.76, t: 0.75 },
601
- { s: 0.93, t: 0.9 },
602
- { s: 1, t: 1 }
603
- ];
604
- if (direction === "in") {
605
- return steps.map(
606
- ({ s, t }) => s === 0 ? `transparent ${(t * percent).toFixed(1)}%` : s === 1 ? `${color} ${(t * percent).toFixed(1)}%` : `color-mix(in srgb, ${color} ${Math.round(s * 100)}%, transparent) ${(t * percent).toFixed(1)}%`
607
- );
608
- }
609
- return steps.map(({ s, t }) => {
610
- const alpha = 1 - s;
611
- return alpha === 0 ? `transparent ${(100 - percent + t * percent).toFixed(1)}%` : alpha === 1 ? `${color} ${(100 - percent + t * percent).toFixed(1)}%` : `color-mix(in srgb, ${color} ${Math.round(alpha * 100)}%, transparent) ${(100 - percent + t * percent).toFixed(1)}%`;
612
- });
466
+ function clearCleanupTimer(scope, cleanupKey) {
467
+ const lifecycle = scope.get(cleanupKey);
468
+ if (!lifecycle) return;
469
+ lifecycle.cancelled = true;
470
+ if (lifecycle.timerId !== null) clearTimeout(lifecycle.timerId);
471
+ scope.delete(cleanupKey);
613
472
  }
614
- function getEdgeFadeMask({
615
- color = "var(--black, #000)",
616
- leftPercent = 0,
617
- rightPercent = 0
618
- }) {
619
- if (leftPercent <= 0 && rightPercent <= 0) return void 0;
620
- const leftStops = leftPercent > 0 ? generateSmoothstepStops(leftPercent, "in", color) : [`${color} 0%`];
621
- const rightStops = rightPercent > 0 ? generateSmoothstepStops(rightPercent, "out", color) : [`${color} 100%`];
622
- return `linear-gradient(to right, ${leftStops.join(", ")}, ${rightStops.join(", ")})`;
473
+ function scheduleCleanup(scope, cleanupKey, callback, delayMs) {
474
+ clearCleanupTimer(scope, cleanupKey);
475
+ const lifecycle = {
476
+ cancelled: false,
477
+ timerId: null
478
+ };
479
+ lifecycle.timerId = setTimeout(() => {
480
+ if (lifecycle.cancelled || scope.get(cleanupKey) !== lifecycle) return;
481
+ scope.delete(cleanupKey);
482
+ callback();
483
+ }, delayMs);
484
+ scope.set(cleanupKey, lifecycle);
623
485
  }
624
- function applyVideoPlaybackState({
625
- isPlaying,
626
- playbackRate,
627
- setVideoPlaying,
628
- videoElement
629
- }) {
630
- if (!videoElement) return;
631
- const numericPlaybackRate = Number(playbackRate);
632
- const resolvedPlaybackRate = Number.isFinite(numericPlaybackRate) && numericPlaybackRate > 0 ? numericPlaybackRate : 1;
633
- try {
634
- if (videoElement.playbackRate !== resolvedPlaybackRate) {
635
- videoElement.playbackRate = resolvedPlaybackRate;
636
- }
637
- } catch {
638
- return;
639
- }
640
- if (!isPlaying) {
641
- if (!videoElement.paused) videoElement.pause();
642
- return;
643
- }
644
- if (videoElement.readyState === 0) {
645
- return;
646
- }
647
- const playPromise = videoElement.play();
648
- if (!playPromise || typeof playPromise.then !== "function") return;
649
- playPromise.then(() => setVideoPlaying(true)).catch((error) => {
650
- if (error?.name === "AbortError" || videoElement.readyState < 2) {
651
- return;
486
+ function collectModuleRegistrations(coreModule, slice, pageMetadata, context) {
487
+ const definition = coreModule.registry;
488
+ const toEntries = coreModule.page?.entries;
489
+ if (!definition || !toEntries || slice == null || slice === false) return [];
490
+ const type = coreModule.id;
491
+ const items = Array.isArray(slice) ? slice : [slice];
492
+ const registrations = [];
493
+ for (const item of items) {
494
+ if (item == null || item === false) continue;
495
+ const itemMetadata = isObject(item) && isObject(item.registry) ? item.registry : {};
496
+ const metadata = { ...pageMetadata, ...itemMetadata };
497
+ if (metadata.validation === "strict" && !validateRegistryMetadata(metadata).valid) {
498
+ continue;
652
499
  }
653
- if (error?.name === "NotAllowedError") {
654
- videoElement.muted = true;
655
- videoElement.play().then(() => setVideoPlaying(true)).catch(() => {
500
+ const payload = isObject(item) ? Object.fromEntries(
501
+ Object.entries(item).filter(([key]) => key !== "registry")
502
+ ) : item;
503
+ const { cleanupDelayMs, lifecycle, registerOptions, scope, source } = normalizeRegistryMetadata(metadata, {
504
+ defaultCleanupDelayMs: definition.cleanupDelayMs ?? null,
505
+ defaultLifecycle: definition.lifecycle,
506
+ defaultSource: DEFAULT_SOURCE
507
+ });
508
+ for (const { key, value } of toEntries(payload, context) ?? []) {
509
+ const validation = definition.validate?.(value);
510
+ if (validation && !validation.valid) {
511
+ report(`Registry ${type} config is invalid`, validation.issues, "warn");
512
+ continue;
513
+ }
514
+ registrations.push({
515
+ cleanupDelayMs,
516
+ cleanupKey: createEntryCleanupKey(
517
+ type,
518
+ key,
519
+ source,
520
+ context.instanceId,
521
+ scope
522
+ ),
523
+ key,
524
+ lifecycle,
525
+ registerOptions,
526
+ scope,
527
+ source,
528
+ type,
529
+ value
656
530
  });
657
- return;
658
- }
659
- setVideoPlaying(false);
660
- });
661
- }
662
- function triggerVideoPlaybackDirect(videoElement, nextPlaying, configuredMuted) {
663
- if (!videoElement) return;
664
- if (nextPlaying) {
665
- videoElement.muted = configuredMuted;
666
- if (!configuredMuted && videoElement.volume === 0) {
667
- videoElement.volume = 0.8;
668
531
  }
669
- videoElement.play().catch((err) => {
670
- if (err?.name === "NotAllowedError") {
671
- videoElement.muted = true;
672
- videoElement.play().catch(() => {
673
- });
674
- }
675
- });
676
- } else {
677
- videoElement.pause();
678
- }
679
- }
680
- function syncVideoMutedDirect(videoElement, nextMuted) {
681
- if (!videoElement) return;
682
- videoElement.muted = nextMuted;
683
- if (!nextMuted && videoElement.volume === 0) {
684
- videoElement.volume = 0.8;
685
532
  }
533
+ return registrations;
686
534
  }
687
- function syncVideoLoopDirect(videoElement, nextLoop) {
688
- if (!videoElement) return;
689
- videoElement.loop = nextLoop;
535
+ var NO_CLEANUP = () => {
536
+ };
537
+ function applyRegistryConfig(config, context) {
538
+ const modules = context.modules ?? [];
539
+ if (!config || modules.length === 0) return NO_CLEANUP;
540
+ const pageMetadata = isObject(config.registry) ? config.registry : {};
541
+ const slices = config;
542
+ const registrations = modules.flatMap((coreModule) => {
543
+ try {
544
+ return collectModuleRegistrations(
545
+ coreModule,
546
+ slices[coreModule.id],
547
+ pageMetadata,
548
+ context
549
+ );
550
+ } catch (error) {
551
+ report(`Registry apply (${coreModule.id})`, error);
552
+ return [];
553
+ }
554
+ });
555
+ if (registrations.length === 0) return NO_CLEANUP;
556
+ const cleanupScope = getCleanupScope(context);
557
+ const owner = (registration) => ({
558
+ instanceId: context.instanceId,
559
+ scope: registration.scope,
560
+ source: registration.source
561
+ });
562
+ runRegistrationBatch(context, (queue) => {
563
+ registrations.forEach(
564
+ ({ cleanupKey, key, registerOptions, source, type, value }) => {
565
+ clearCleanupTimer(cleanupScope, cleanupKey);
566
+ queue.register(type, key, value, source, registerOptions);
567
+ }
568
+ );
569
+ });
570
+ return () => {
571
+ const immediate = [];
572
+ registrations.forEach((registration) => {
573
+ if (isPersistentLifecycle(registration.lifecycle)) return;
574
+ const { cleanupDelayMs, cleanupKey, key, type } = registration;
575
+ if (typeof cleanupDelayMs === "number" && cleanupDelayMs > 0) {
576
+ scheduleCleanup(
577
+ cleanupScope,
578
+ cleanupKey,
579
+ () => context.unregister(type, key, owner(registration)),
580
+ cleanupDelayMs
581
+ );
582
+ } else {
583
+ immediate.push(registration);
584
+ }
585
+ });
586
+ if (immediate.length === 0) return;
587
+ runRegistrationBatch(context, (queue) => {
588
+ immediate.forEach(
589
+ (registration) => queue.unregister(
590
+ registration.type,
591
+ registration.key,
592
+ owner(registration)
593
+ )
594
+ );
595
+ });
596
+ };
690
597
  }
691
598
 
692
- // src/modules/background/motion.ts
693
- var DEFAULT_MOTION = Object.freeze({
694
- animate: Object.freeze({
695
- opacity: 1
696
- }),
697
- exit: Object.freeze({
698
- opacity: 0,
699
- transition: Object.freeze({
700
- duration: DURATION_TOKENS.SLOW,
701
- ease: EASING_CURVES.IN_CUBIC
702
- })
703
- }),
704
- initial: Object.freeze({
705
- opacity: 0
706
- }),
707
- transition: Object.freeze({
708
- delay: 0,
709
- duration: DURATION_TOKENS.SLOW,
710
- ease: EASING_CURVES.OUT_QUART
711
- })
712
- });
713
- function getBackgroundMotionConfig(animation) {
599
+ // src/kernel/hooks.ts
600
+ function withInstanceId(instanceId, sourceOrOptions, optionsArg) {
601
+ if (typeof sourceOrOptions === "string") {
602
+ return {
603
+ optionsArg: { ...isObject(optionsArg) ? optionsArg : {}, instanceId },
604
+ sourceOrOptions
605
+ };
606
+ }
607
+ if (isObject(sourceOrOptions)) {
608
+ return {
609
+ optionsArg,
610
+ sourceOrOptions: { ...sourceOrOptions, instanceId }
611
+ };
612
+ }
714
613
  return {
715
- animate: animation?.animate ?? DEFAULT_MOTION.animate,
716
- exit: animation?.exit ?? DEFAULT_MOTION.exit,
717
- exitDurationFactor: Number(animation?.exitDurationFactor),
718
- initial: animation?.initial ?? DEFAULT_MOTION.initial,
719
- transition: animation?.transition ?? DEFAULT_MOTION.transition
614
+ optionsArg: void 0,
615
+ sourceOrOptions: {
616
+ ...isObject(optionsArg) ? optionsArg : {},
617
+ instanceId
618
+ }
720
619
  };
721
620
  }
722
- function toCssDuration(seconds) {
723
- const value = Number(seconds);
724
- return `${Math.max(0, Number.isFinite(value) ? value : DURATION_TOKENS.SLOW) * 1e3}ms`;
725
- }
726
- function toCssDelay(seconds) {
727
- const value = Number(seconds);
728
- return `${Math.max(0, Number.isFinite(value) ? value : 0) * 1e3}ms`;
729
- }
730
- function toCssEasing(easing) {
731
- if (Array.isArray(easing)) {
732
- return `cubic-bezier(${easing.join(", ")})`;
621
+ function withInstanceIdForUnregister(instanceId, sourceOrOptions) {
622
+ if (typeof sourceOrOptions === "string") {
623
+ return { instanceId, source: sourceOrOptions };
733
624
  }
734
- if (typeof easing === "string" && easing.trim()) {
735
- return easing;
625
+ if (isObject(sourceOrOptions)) {
626
+ return { ...sourceOrOptions, instanceId };
736
627
  }
737
- return "ease";
628
+ return { instanceId };
738
629
  }
739
- function useRequiredContext(context, hookName, providerName) {
740
- const value = use(context);
741
- if (value === null) {
742
- throw new Error(`${hookName} must be used within ${providerName}`);
743
- }
744
- return value;
630
+ function resolveRegisterArgsWithInstance(instanceId, sourceOrOptions, optionsArg) {
631
+ const input = withInstanceId(instanceId, sourceOrOptions, optionsArg);
632
+ return [input.sourceOrOptions, input.optionsArg];
745
633
  }
746
- function useStore(store, selector = (state) => state, isEqual = Object.is) {
747
- const cacheRef = useRef({
748
- isEqual: null,
749
- selected: void 0,
750
- selector: null,
751
- snapshot: null
752
- });
753
- const getSelectedSnapshot = useCallback(() => {
754
- const snapshot = store.getSnapshot();
755
- const cache = cacheRef.current;
756
- if (cache.snapshot === snapshot && cache.selector === selector && cache.isEqual === isEqual) {
757
- return cache.selected;
758
- }
759
- const selected = selector(snapshot);
760
- if (cache.snapshot !== null && cache.isEqual === isEqual && isEqual(cache.selected, selected)) {
761
- cache.snapshot = snapshot;
762
- cache.selector = selector;
763
- return cache.selected;
634
+ var MAX_STABILIZATION_DEPTH = 64;
635
+ function createStableFunctionEntry(fn) {
636
+ const entry = {
637
+ current: fn,
638
+ stable(...args) {
639
+ return entry.current?.apply(this, args);
764
640
  }
765
- cacheRef.current = {
766
- isEqual,
767
- selected,
768
- selector,
769
- snapshot
770
- };
771
- return selected;
772
- }, [isEqual, selector, store]);
773
- return useSyncExternalStore(
774
- store.subscribe,
775
- getSelectedSnapshot,
776
- getSelectedSnapshot
777
- );
641
+ };
642
+ return entry;
778
643
  }
779
- var useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect;
780
- var EventEmitter = class {
781
- events = /* @__PURE__ */ new Map();
782
- debounceTimers = /* @__PURE__ */ new Map();
783
- subscribe(event, callback) {
784
- if (typeof event !== "string" || !event || typeof callback !== "function") {
785
- return () => {
786
- };
787
- }
788
- const listeners = this.events.get(event) || /* @__PURE__ */ new Set();
789
- listeners.add(callback);
790
- this.events.set(event, listeners);
791
- return () => {
792
- listeners.delete(callback);
793
- if (listeners.size === 0) this.events.delete(event);
794
- };
644
+ function isDirectRegistryComponentPath(path, componentSlices) {
645
+ const match = /^config\.([^.[]+)\.[^.[]+$/.exec(path);
646
+ return Boolean(match && componentSlices.has(match[1]));
647
+ }
648
+ function isReactNodeLike(value) {
649
+ return value === null || value === void 0 || typeof value === "boolean" || typeof value === "string" || typeof value === "number" || isValidElement(value);
650
+ }
651
+ function isStabilizableObject(value) {
652
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !isValidElement(value);
653
+ }
654
+ function hasSameObjectKeys(previousValue, nextValue) {
655
+ if (!isStabilizableObject(previousValue) || !isStabilizableObject(nextValue)) {
656
+ return false;
795
657
  }
796
- emit(event, payload) {
797
- const listeners = this.events.get(event);
798
- if (!listeners || listeners.size === 0) return;
799
- const snapshot = Array.from(listeners);
800
- for (const callback of snapshot) {
801
- try {
802
- callback(payload);
803
- } catch (error) {
804
- report(`Event listener (${String(event)})`, error);
805
- }
658
+ const previousKeys = Object.keys(previousValue);
659
+ const nextKeys = Object.keys(nextValue);
660
+ if (previousKeys.length !== nextKeys.length) return false;
661
+ return nextKeys.every(
662
+ (key) => Object.prototype.hasOwnProperty.call(previousValue, key)
663
+ );
664
+ }
665
+ function stabilizeRegistryValue(value, path, scope, previousValue, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
666
+ if (depth > MAX_STABILIZATION_DEPTH) return value;
667
+ if (typeof value === "function") {
668
+ const isComponent = value.name && /^[A-Z]/.test(value.name);
669
+ if (isComponent || isDirectRegistryComponentPath(path, scope.componentSlices)) {
670
+ return value;
806
671
  }
807
- }
808
- emitDebounced(event, payload, waitMs = 100) {
809
- const existing = this.debounceTimers.get(event);
810
- if (existing) clearTimeout(existing);
811
- const timer = setTimeout(() => {
812
- this.debounceTimers.delete(event);
813
- this.emit(event, payload);
814
- }, waitMs);
815
- this.debounceTimers.set(event, timer);
816
- }
817
- cancelDebounced(event) {
818
- if (event) {
819
- const timer = this.debounceTimers.get(event);
820
- if (timer) {
821
- clearTimeout(timer);
822
- this.debounceTimers.delete(event);
823
- }
672
+ scope.usedPaths.add(path);
673
+ let entry = scope.functionEntries.get(path);
674
+ if (!entry) {
675
+ entry = createStableFunctionEntry(value);
676
+ scope.functionEntries.set(path, entry);
824
677
  } else {
825
- for (const timer of this.debounceTimers.values()) {
826
- clearTimeout(timer);
827
- }
828
- this.debounceTimers.clear();
678
+ entry.current = value;
829
679
  }
680
+ return entry.stable;
830
681
  }
831
- unsubscribeAll(event) {
832
- this.cancelDebounced(event);
833
- if (event) this.events.delete(event);
834
- else this.events.clear();
835
- }
836
- hasListeners(event) {
837
- return Boolean(this.events.get(event)?.size);
838
- }
839
- getListenerCount(event) {
840
- return this.events.get(event)?.size || 0;
682
+ if (value && typeof value === "object") {
683
+ if (seen.has(value)) return value;
684
+ seen.add(value);
841
685
  }
842
- getAllEvents() {
843
- return [...this.events.keys()];
686
+ if (isValidElement(value)) {
687
+ const nextProps = stabilizeRegistryValue(
688
+ value.props,
689
+ `${path}.props`,
690
+ scope,
691
+ isValidElement(previousValue) ? previousValue.props : void 0,
692
+ seen,
693
+ depth + 1
694
+ );
695
+ if (isValidElement(previousValue) && previousValue.type === value.type && previousValue.key === value.key && previousValue.props === nextProps) {
696
+ return previousValue;
697
+ }
698
+ return cloneElement(value, nextProps);
844
699
  }
845
- };
846
- var GLOBAL_EVENTS_KEY = /* @__PURE__ */ Symbol.for("__base_framework_global_events__");
847
- var _global = globalThis;
848
- _global[GLOBAL_EVENTS_KEY] ?? (_global[GLOBAL_EVENTS_KEY] = new EventEmitter());
849
-
850
- // src/kernel/constants.ts
851
- var DEFAULT_SOURCE = "dynamic";
852
- var REGISTRY_SOURCES = Object.freeze({
853
- STATIC: "static",
854
- DYNAMIC: "dynamic",
855
- USER: "user"
856
- });
857
- var REGISTRY_LIFECYCLES = Object.freeze({
858
- IMMEDIATE: "immediate",
859
- GRACEFUL: "graceful",
860
- PERSISTENT: "persistent",
861
- ROUTE: "route"
862
- });
863
- var REGISTRY_VALIDATION_MODES = Object.freeze({
864
- WARN: "warn",
865
- STRICT: "strict"
866
- });
867
- var REGISTRY_METADATA_KEYS = Object.freeze([
868
- "cleanup",
869
- "cleanupDelayMs",
870
- "instanceId",
871
- "lifecycle",
872
- "priority",
873
- "source",
874
- "scope",
875
- "validation"
876
- ]);
877
- Object.freeze({
878
- [REGISTRY_SOURCES.STATIC]: 100,
879
- [REGISTRY_SOURCES.DYNAMIC]: 200,
880
- [REGISTRY_SOURCES.USER]: 300
881
- });
882
- Object.freeze({
883
- [REGISTRY_SOURCES.STATIC]: 10,
884
- [REGISTRY_SOURCES.DYNAMIC]: 20,
885
- [REGISTRY_SOURCES.USER]: 30
886
- });
887
- var REGISTRY_LIFECYCLE_VALUES = new Set(
888
- Object.values(REGISTRY_LIFECYCLES)
889
- );
890
- var REGISTRY_METADATA_KEY_SET = new Set(
891
- REGISTRY_METADATA_KEYS
892
- );
893
- var REGISTRY_SCOPE_KINDS = Object.freeze({
894
- APP: "app",
895
- SESSION: "session",
896
- ROUTE: "route",
897
- INSTANCE: "instance",
898
- WORKSPACE: "workspace"
899
- });
900
- REGISTRY_SCOPE_KINDS.APP;
901
-
902
- // src/kernel/utils.ts
903
- function parseFiniteNumber(value) {
904
- const parsed = Number(value);
905
- return Number.isFinite(parsed) ? parsed : null;
906
- }
907
- function getValidString(value, fallback = null) {
908
- return trimToNull(value) ?? fallback;
909
- }
910
-
911
- // src/kernel/schema.ts
912
- function resolveLifecycle(metadata, fallback) {
913
- const lifecycle = metadata?.lifecycle ?? metadata?.cleanup;
914
- return lifecycle && REGISTRY_LIFECYCLE_VALUES.has(lifecycle) ? lifecycle : fallback;
915
- }
916
- function validateRegistryMetadata(metadata) {
917
- if (metadata === void 0 || metadata === null)
918
- return { issues: [], valid: true };
919
- if (!isObject(metadata))
920
- return { issues: ["registry metadata must be an object"], valid: false };
921
- const issues = [];
922
- Object.keys(metadata).forEach((key) => {
923
- if (!REGISTRY_METADATA_KEY_SET.has(key))
924
- issues.push(`unknown registry metadata field: ${key}`);
925
- });
926
- if (metadata.source !== void 0 && !getValidString(metadata.source))
927
- issues.push("source must be a non-empty string");
928
- if (metadata.instanceId !== void 0 && !getValidString(metadata.instanceId))
929
- issues.push("instanceId must be a non-empty string");
930
- if (metadata.scope !== void 0 && !getValidString(metadata.scope))
931
- issues.push("scope must be a non-empty string");
932
- if (metadata.priority !== void 0 && parseFiniteNumber(metadata.priority) === null)
933
- issues.push("priority must be a finite number");
934
- if (metadata.cleanupDelayMs !== void 0) {
935
- const delay = parseFiniteNumber(metadata.cleanupDelayMs);
936
- if (delay === null || delay < 0)
937
- issues.push("cleanupDelayMs must be a non-negative finite number");
700
+ if (Array.isArray(value)) {
701
+ const nextValue = value.every(isReactNodeLike) ? Children.toArray(value) : value;
702
+ const previousArray = Array.isArray(previousValue) ? previousValue : null;
703
+ let hasChanged2 = !previousArray || previousArray.length !== nextValue.length;
704
+ const stabilizedValue2 = nextValue.map((item, index) => {
705
+ const nextItem = stabilizeRegistryValue(
706
+ item,
707
+ `${path}[${index}]`,
708
+ scope,
709
+ previousArray?.[index],
710
+ seen,
711
+ depth + 1
712
+ );
713
+ if (!previousArray || nextItem !== previousArray[index]) {
714
+ hasChanged2 = true;
715
+ }
716
+ return nextItem;
717
+ });
718
+ return !hasChanged2 ? previousArray : stabilizedValue2;
938
719
  }
939
- const lifecycle = metadata.lifecycle ?? metadata.cleanup;
940
- if (lifecycle !== void 0 && !REGISTRY_LIFECYCLE_VALUES.has(String(lifecycle))) {
941
- issues.push(
942
- `lifecycle must be one of: ${[...REGISTRY_LIFECYCLE_VALUES].join(", ")}`
943
- );
720
+ if (typeof value !== "object" || value === null) {
721
+ return value;
944
722
  }
945
- if (metadata.validation !== void 0 && !Object.values(REGISTRY_VALIDATION_MODES).includes(
946
- metadata.validation
947
- )) {
948
- issues.push(
949
- `validation must be one of: ${Object.values(REGISTRY_VALIDATION_MODES).join(", ")}`
723
+ const source = value;
724
+ const previousRecord = previousValue;
725
+ const stabilizedValue = {};
726
+ const canReusePrevious = hasSameObjectKeys(previousValue, value);
727
+ let hasChanged = !canReusePrevious;
728
+ Object.keys(source).forEach((key) => {
729
+ const nextValue = stabilizeRegistryValue(
730
+ source[key],
731
+ `${path}.${key}`,
732
+ scope,
733
+ previousRecord?.[key],
734
+ seen,
735
+ depth + 1
950
736
  );
951
- }
952
- return { issues, valid: issues.length === 0 };
737
+ stabilizedValue[key] = nextValue;
738
+ if (!canReusePrevious || nextValue !== previousRecord?.[key]) {
739
+ hasChanged = true;
740
+ }
741
+ });
742
+ return !hasChanged ? previousValue : stabilizedValue;
953
743
  }
954
- function normalizeRegistryMetadata(metadata, {
955
- defaultCleanupDelayMs = null,
956
- defaultLifecycle = null,
957
- defaultSource = DEFAULT_SOURCE
958
- } = {}) {
959
- const registryMeta = isObject(metadata) ? metadata : {};
960
- const normalizedDefaultDelay = parseFiniteNumber(defaultCleanupDelayMs);
961
- const fallbackCleanupDelayMs = normalizedDefaultDelay !== null && normalizedDefaultDelay >= 0 ? normalizedDefaultDelay : null;
962
- const fallbackLifecycle = defaultLifecycle && REGISTRY_LIFECYCLE_VALUES.has(defaultLifecycle) ? defaultLifecycle : null;
963
- const source = getValidString(registryMeta.source) || getValidString(defaultSource, DEFAULT_SOURCE) || DEFAULT_SOURCE;
964
- const scope = getValidString(registryMeta.scope);
965
- const priority = parseFiniteNumber(registryMeta.priority);
966
- const lifecycle = resolveLifecycle(registryMeta, fallbackLifecycle);
967
- const validation = isValidationMode(registryMeta.validation) ? registryMeta.validation : null;
968
- const cleanupDelayCandidate = parseFiniteNumber(registryMeta.cleanupDelayMs);
969
- let cleanupDelayMs = cleanupDelayCandidate !== null && cleanupDelayCandidate >= 0 ? cleanupDelayCandidate : fallbackCleanupDelayMs;
970
- if (lifecycle === REGISTRY_LIFECYCLES.IMMEDIATE) {
971
- cleanupDelayMs = 0;
972
- } else if ((lifecycle === REGISTRY_LIFECYCLES.GRACEFUL || lifecycle === REGISTRY_LIFECYCLES.ROUTE) && cleanupDelayMs === null) {
973
- cleanupDelayMs = fallbackCleanupDelayMs;
974
- }
975
- return {
976
- cleanupDelayMs,
977
- lifecycle,
978
- priority,
979
- registerOptions: {
980
- ...priority !== null && { priority },
981
- ...validation === REGISTRY_VALIDATION_MODES.STRICT && { validation },
982
- ...scope && { scope }
744
+ function useAppliedRegistryConfig(payload, context, componentSlices) {
745
+ const functionEntriesRef = useRef(/* @__PURE__ */ new Map());
746
+ const appliedRef = useRef(null);
747
+ useIsomorphicLayoutEffect(() => {
748
+ const previous = appliedRef.current;
749
+ if (previous && previous.payload === payload && previous.context === context) {
750
+ return;
751
+ }
752
+ const functionEntries = functionEntriesRef.current;
753
+ const usedPaths = /* @__PURE__ */ new Set();
754
+ const config = stabilizeRegistryValue(
755
+ payload,
756
+ "config",
757
+ { componentSlices, functionEntries, usedPaths },
758
+ previous?.config
759
+ );
760
+ functionEntries.forEach((_entry, path) => {
761
+ if (!usedPaths.has(path)) functionEntries.delete(path);
762
+ });
763
+ if (previous && previous.config === config && previous.context === context) {
764
+ previous.payload = payload;
765
+ return;
766
+ }
767
+ previous?.cleanup();
768
+ appliedRef.current = {
769
+ cleanup: applyRegistryConfig(config, context),
770
+ config,
771
+ context,
772
+ payload
773
+ };
774
+ });
775
+ useIsomorphicLayoutEffect(
776
+ () => () => {
777
+ appliedRef.current?.cleanup();
778
+ appliedRef.current = null;
983
779
  },
984
- source,
985
- ...scope && { scope },
986
- ...validation && { validation }
987
- };
988
- }
989
- function isValidationMode(value) {
990
- return Object.values(REGISTRY_VALIDATION_MODES).includes(value);
991
- }
992
- function pickRegistryMetadata(options) {
993
- if (!isObject(options)) return {};
994
- return Object.fromEntries(
995
- REGISTRY_METADATA_KEYS.filter((key) => options[key] !== void 0).map(
996
- (key) => [key, options[key]]
997
- )
780
+ []
998
781
  );
999
782
  }
1000
- function createNoopHandle(status = "rejected", reason = "unknown") {
1001
- const handle = Object.assign(() => false, {
1002
- active: false,
1003
- dispose: () => false,
1004
- instanceId: null,
1005
- key: "",
1006
- priority: 0,
1007
- reason,
1008
- source: "",
1009
- status,
1010
- type: "",
1011
- update: () => handle,
1012
- updatedAt: 0,
1013
- validation: "warn"
783
+ function createRegistryApplyContext(actions, {
784
+ cleanupScope = /* @__PURE__ */ new Map(),
785
+ instanceId,
786
+ modules = [],
787
+ pathname
788
+ }) {
789
+ const stamp = (queue) => ({
790
+ register: (type, key, item, sourceOrOptions, optionsArg) => queue.register(
791
+ type,
792
+ key,
793
+ item,
794
+ ...resolveRegisterArgsWithInstance(
795
+ instanceId,
796
+ sourceOrOptions,
797
+ optionsArg
798
+ )
799
+ ),
800
+ unregister: (type, key, sourceOrOptions) => queue.unregister(
801
+ type,
802
+ key,
803
+ withInstanceIdForUnregister(instanceId, sourceOrOptions)
804
+ )
1014
805
  });
1015
- return handle;
806
+ return {
807
+ ...stamp(actions),
808
+ batch: (executor) => typeof executor === "function" ? actions.batch((queue) => executor(stamp(queue))) : 0,
809
+ cleanupScope,
810
+ instanceId,
811
+ modules,
812
+ pathname
813
+ };
1016
814
  }
1017
- createNoopHandle();
1018
- var RegistryContext = getOrCreateGlobalContext(
1019
- "RegistryContext",
1020
- null
1021
- );
1022
- function useRegistryActions() {
1023
- return useRequiredContext(
1024
- RegistryContext,
1025
- "useRegistryActions",
1026
- "RegistryProvider"
1027
- ).actions;
815
+ function useRegistry(config, metadata) {
816
+ const { batch, register, unregister } = useRegistryActions();
817
+ const pathname = usePathname();
818
+ const modules = useInstalledModules();
819
+ const instanceId = `registry-instance-${useId()}`;
820
+ const [cleanupScope] = useState(
821
+ () => /* @__PURE__ */ new Map()
822
+ );
823
+ const context = useMemo(
824
+ () => createRegistryApplyContext(
825
+ { batch, register, unregister },
826
+ {
827
+ cleanupScope,
828
+ instanceId,
829
+ modules,
830
+ pathname
831
+ }
832
+ ),
833
+ [batch, cleanupScope, instanceId, modules, pathname, register, unregister]
834
+ );
835
+ const payload = useMemo(
836
+ () => config ? createModulePayload(modules, config, metadata) : null,
837
+ [config, metadata, modules]
838
+ );
839
+ const componentSlices = useMemo(
840
+ () => new Set(
841
+ modules.filter(
842
+ (coreModule) => coreModule.registry?.valueKind === "component"
843
+ ).map((coreModule) => coreModule.id)
844
+ ),
845
+ [modules]
846
+ );
847
+ useAppliedRegistryConfig(payload, context, componentSlices);
1028
848
  }
1029
- function useRegistrySubscription() {
1030
- return useRequiredContext(
1031
- RegistryContext,
1032
- "useRegistrySubscription",
1033
- "RegistryProvider"
1034
- ).subscription;
849
+ function defineModule(coreModule) {
850
+ return Object.freeze({ ...coreModule });
1035
851
  }
1036
- function useRegistryValue(type, key) {
1037
- const { getSnapshot, subscribe } = useRegistrySubscription();
1038
- const subscribeToKey = useCallback(
1039
- (listener) => subscribe(type, key, listener),
1040
- [key, subscribe, type]
1041
- );
1042
- const getValue = useCallback(
1043
- () => getSnapshot(type, key),
1044
- [getSnapshot, key, type]
852
+ createStore(void 0);
853
+
854
+ // src/kernel/adapters.ts
855
+ function useModuleRegistration(id, value, options) {
856
+ const coreModule = useInstalledModules().find(
857
+ (candidate) => candidate.id === id
1045
858
  );
1046
- return useSyncExternalStore(subscribeToKey, getValue, getValue);
859
+ const config = coreModule && options?.enabled !== false && value != null ? { [id]: value } : null;
860
+ useRegistry(config, pickRegistryMetadata(options));
1047
861
  }
1048
- var EMPTY_MODULES = Object.freeze([]);
1049
- var ModuleHostContext = getOrCreateGlobalContext("ModuleHostContext", null);
1050
- function selectModuleSlice(coreModule, config) {
1051
- const slice = coreModule.page?.select ? coreModule.page.select(config) : config[coreModule.id];
1052
- return slice ?? null;
862
+
863
+ // src/theme.tsx
864
+ function defineThemeSpec(id) {
865
+ return Object.freeze({ id });
1053
866
  }
1054
- function createModulePayload(modules, config, metadata = {}) {
1055
- const payload = {};
1056
- let hasSlices = false;
1057
- for (const coreModule of modules) {
1058
- if (!coreModule.page?.entries) continue;
1059
- const slice = selectModuleSlice(coreModule, config);
1060
- if (slice === null) continue;
1061
- payload[coreModule.id] = slice;
1062
- hasSlices = true;
867
+ var EMPTY = /* @__PURE__ */ new Map();
868
+ var ThemeContext = getOrCreateGlobalContext("ThemeContext", EMPTY);
869
+ function useTheme(spec) {
870
+ const config = useContext(ThemeContext).get(spec.id);
871
+ if (!config) {
872
+ throw new Error(
873
+ `Theme "${spec.id}" is missing. Ensure its theme configuration is provided to <ThemeProvider themes={[...]}> (e.g. in src/config/ or your theme setup).`
874
+ );
1063
875
  }
1064
- if (!hasSlices) return null;
1065
876
  return {
1066
- ...payload,
1067
- registry: {
1068
- ...metadata,
1069
- ...isObject(config.registry) ? config.registry : {}
1070
- }
877
+ slots: config.slots,
878
+ styles: config.styles ?? {}
1071
879
  };
1072
880
  }
1073
- function useInstalledModules() {
1074
- return use(ModuleHostContext)?.modules ?? EMPTY_MODULES;
1075
- }
1076
881
 
1077
- // src/kernel/handlers.ts
1078
- function isPersistentLifecycle(lifecycle) {
1079
- return lifecycle === REGISTRY_LIFECYCLES.PERSISTENT;
882
+ // src/modules/background/constants.ts
883
+ var DEFAULT_BACKGROUND = Object.freeze({
884
+ animation: null,
885
+ className: "",
886
+ fadeEdges: null,
887
+ fit: null,
888
+ image: null,
889
+ imageStyle: {},
890
+ isPlaying: false,
891
+ noiseStyle: {},
892
+ overlay: false,
893
+ overlayColor: "var(--black)",
894
+ overlayOpacity: 0,
895
+ position: "center",
896
+ video: null,
897
+ videoClassName: "",
898
+ videoElement: null,
899
+ videoOptions: {
900
+ autoplay: true,
901
+ className: "",
902
+ corp: 0,
903
+ loop: false,
904
+ muted: true,
905
+ playbackRate: 1,
906
+ width: null
907
+ },
908
+ videoStyle: {},
909
+ width: null
910
+ });
911
+ var DEFAULT_BACKGROUND_COMPUTED = Object.freeze({
912
+ ...DEFAULT_BACKGROUND,
913
+ hasBackground: false,
914
+ isVideo: false,
915
+ isYouTube: false,
916
+ posterUrl: null,
917
+ youtubeVideoId: null
918
+ });
919
+ var INERT_BACKGROUND_ACTIONS = Object.freeze({
920
+ resetBackground: () => {
921
+ },
922
+ setBackground: () => {
923
+ },
924
+ setVideoElement: () => {
925
+ },
926
+ setVideoMuted: () => {
927
+ },
928
+ setVideoPlaying: () => {
929
+ },
930
+ toggleLoop: () => {
931
+ },
932
+ toggleMute: () => {
933
+ },
934
+ toggleVideo: () => {
935
+ }
936
+ });
937
+ var objectFit = (value) => ({
938
+ objectFit: value
939
+ });
940
+ var objectPosition = (value) => ({
941
+ objectPosition: value
942
+ });
943
+ var BG_TOKEN_TO_OBJECT_STYLE = Object.freeze({
944
+ "bg-bottom": objectPosition("bottom"),
945
+ "bg-center": objectPosition("center"),
946
+ "bg-contain": objectFit("contain"),
947
+ "bg-cover": objectFit("cover"),
948
+ "bg-fill": objectFit("fill"),
949
+ "bg-left": objectPosition("left"),
950
+ "bg-left-bottom": objectPosition("left bottom"),
951
+ "bg-left-top": objectPosition("left top"),
952
+ "bg-none": objectFit("none"),
953
+ "bg-right": objectPosition("right"),
954
+ "bg-right-bottom": objectPosition("right bottom"),
955
+ "bg-right-top": objectPosition("right top"),
956
+ "bg-scale-down": objectFit("scale-down"),
957
+ "bg-top": objectPosition("top")
958
+ });
959
+ var OBJECT_FITS = Object.freeze([
960
+ "contain",
961
+ "cover",
962
+ "fill",
963
+ "none",
964
+ "scale-down"
965
+ ]);
966
+ var DEFAULT_COLOR = "var(--black, #000)";
967
+ var backgroundTheme = defineThemeSpec("background");
968
+ var BACKGROUND_REGISTRY_KEY = "page-background";
969
+ var BACKGROUND_MEDIA_ID = "background";
970
+
971
+ // src/modules/background/youtube/parse.ts
972
+ var YOUTUBE_ID_PATTERN = /^[a-zA-Z0-9_-]{11}$/;
973
+ function parseYouTubeTimeParam(value) {
974
+ if (value === void 0 || value === null || value === "") return 0;
975
+ if (typeof value === "number") {
976
+ return Number.isFinite(value) && value >= 0 ? value : 0;
977
+ }
978
+ const trimmed = String(value).trim().toLowerCase();
979
+ if (!trimmed) return 0;
980
+ if (/^\d+(\.\d+)?$/.test(trimmed)) {
981
+ return Math.max(0, parseFloat(trimmed));
982
+ }
983
+ const hmsMatch = trimmed.match(
984
+ /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+(?:\.\d+)?)s?)?$/
985
+ );
986
+ if (hmsMatch && (hmsMatch[1] || hmsMatch[2] || hmsMatch[3])) {
987
+ const hours = parseInt(hmsMatch[1] || "0", 10);
988
+ const minutes = parseInt(hmsMatch[2] || "0", 10);
989
+ const seconds = parseFloat(hmsMatch[3] || "0");
990
+ return Math.max(0, hours * 3600 + minutes * 60 + seconds);
991
+ }
992
+ return 0;
1080
993
  }
1081
- function runRegistrationBatch(context, executor) {
1082
- return typeof context.batch === "function" ? context.batch(executor) : executor(context);
994
+ var YOUTUBE_HOST_PATH = /^(?:www\.|m\.|music\.)?(?:youtube\.com|youtu\.be|youtube-nocookie\.com)\//i;
995
+ function toUrl(input, { youtubeOnly = false } = {}) {
996
+ let candidate = null;
997
+ if (/^https?:\/\//.test(input)) candidate = input;
998
+ else if (input.startsWith("//")) candidate = `https:${input}`;
999
+ else if (!youtubeOnly || YOUTUBE_HOST_PATH.test(input)) {
1000
+ candidate = `https://${input}`;
1001
+ }
1002
+ try {
1003
+ return candidate ? new URL(candidate) : null;
1004
+ } catch {
1005
+ return null;
1006
+ }
1083
1007
  }
1084
- function createEntryCleanupKey(type, key, source, instanceId, scope) {
1085
- return JSON.stringify([
1086
- "entry",
1087
- type,
1088
- key,
1089
- scope ?? null,
1090
- source,
1091
- instanceId
1092
- ]);
1008
+ function getIdFromUrl(url) {
1009
+ const host = url.hostname.replace(/^(www\.|m\.|music\.)/i, "").toLowerCase();
1010
+ const segments = url.pathname.split("/").filter(Boolean);
1011
+ if (host === "youtu.be") {
1012
+ return YOUTUBE_ID_PATTERN.test(segments[0] ?? "") ? segments[0] : null;
1013
+ }
1014
+ if (host !== "youtube.com" && host !== "youtube-nocookie.com" && !host.endsWith(".youtube.com")) {
1015
+ return null;
1016
+ }
1017
+ const fromQuery = url.searchParams.get("v") || url.searchParams.get("vi");
1018
+ if (fromQuery && YOUTUBE_ID_PATTERN.test(fromQuery)) return fromQuery;
1019
+ const isVideoPath = segments.length >= 2 && ["embed", "shorts", "live", "v", "e", "watch"].includes(
1020
+ segments[0].toLowerCase()
1021
+ );
1022
+ return isVideoPath && YOUTUBE_ID_PATTERN.test(segments[1]) ? segments[1] : null;
1023
+ }
1024
+ function extractYouTubeVideoId(input, options = {}) {
1025
+ const trimmed = typeof input === "string" ? input.trim() : "";
1026
+ if (!trimmed) return null;
1027
+ if (options.allowBareId && YOUTUBE_ID_PATTERN.test(trimmed)) return trimmed;
1028
+ const prefixed = trimmed.match(/^(?:youtube|yt):([a-zA-Z0-9_-]{11})$/i);
1029
+ if (prefixed) return prefixed[1];
1030
+ const url = toUrl(trimmed, { youtubeOnly: true });
1031
+ return url ? getIdFromUrl(url) : null;
1093
1032
  }
1094
- function getCleanupScope(context) {
1095
- return context?.cleanupScope || /* @__PURE__ */ new Map();
1033
+ function isYouTubeUrl(input) {
1034
+ return Boolean(extractYouTubeVideoId(input));
1096
1035
  }
1097
- function clearCleanupTimer(scope, cleanupKey) {
1098
- const lifecycle = scope.get(cleanupKey);
1099
- if (!lifecycle) return;
1100
- lifecycle.cancelled = true;
1101
- if (lifecycle.timerId !== null) clearTimeout(lifecycle.timerId);
1102
- scope.delete(cleanupKey);
1036
+ function isDirectVideoUrl(input) {
1037
+ if (!input || typeof input !== "string") return false;
1038
+ const trimmed = input.trim();
1039
+ if (!trimmed) return false;
1040
+ if (isYouTubeUrl(trimmed)) return true;
1041
+ if (trimmed.startsWith("/api/background/youtube") || trimmed.startsWith("blob:")) {
1042
+ return true;
1043
+ }
1044
+ const cleanPath = trimmed.split("?")[0].split("#")[0].toLowerCase();
1045
+ return /\.(mp4|webm|mov|m4v|ogg|ogv|m3u8)$/i.test(cleanPath);
1103
1046
  }
1104
- function scheduleCleanup(scope, cleanupKey, callback, delayMs) {
1105
- clearCleanupTimer(scope, cleanupKey);
1106
- const lifecycle = {
1107
- cancelled: false,
1108
- timerId: null
1047
+ function parseYouTubeUrlConfig(input, options = {}) {
1048
+ const videoId = extractYouTubeVideoId(input, options);
1049
+ if (!videoId || !input) return null;
1050
+ const params = toUrl(input.trim())?.searchParams;
1051
+ return {
1052
+ endTime: parseYouTubeTimeParam(params?.get("end")),
1053
+ listId: params?.get("list") || null,
1054
+ rawUrl: input,
1055
+ startTime: parseYouTubeTimeParam(params?.get("t") || params?.get("start")),
1056
+ videoId
1109
1057
  };
1110
- lifecycle.timerId = setTimeout(() => {
1111
- if (lifecycle.cancelled || scope.get(cleanupKey) !== lifecycle) return;
1112
- scope.delete(cleanupKey);
1113
- callback();
1114
- }, delayMs);
1115
- scope.set(cleanupKey, lifecycle);
1116
1058
  }
1117
- function collectModuleRegistrations(coreModule, slice, pageMetadata, context) {
1118
- const definition = coreModule.registry;
1119
- const toEntries = coreModule.page?.entries;
1120
- if (!definition || !toEntries || slice == null || slice === false) return [];
1121
- const type = coreModule.id;
1122
- const items = Array.isArray(slice) ? slice : [slice];
1123
- const registrations = [];
1124
- for (const item of items) {
1125
- if (item == null || item === false) continue;
1126
- const itemMetadata = isObject(item) && isObject(item.registry) ? item.registry : {};
1127
- const metadata = { ...pageMetadata, ...itemMetadata };
1128
- if (metadata.validation === "strict" && !validateRegistryMetadata(metadata).valid) {
1129
- continue;
1130
- }
1131
- const payload = isObject(item) ? Object.fromEntries(
1132
- Object.entries(item).filter(([key]) => key !== "registry")
1133
- ) : item;
1134
- const { cleanupDelayMs, lifecycle, registerOptions, scope, source } = normalizeRegistryMetadata(metadata, {
1135
- defaultCleanupDelayMs: definition.cleanupDelayMs ?? null,
1136
- defaultLifecycle: definition.lifecycle,
1137
- defaultSource: DEFAULT_SOURCE
1138
- });
1139
- for (const { key, value } of toEntries(payload, context) ?? []) {
1140
- const validation = definition.validate?.(value);
1141
- if (validation && !validation.valid) {
1142
- report(`Registry ${type} config is invalid`, validation.issues, "warn");
1143
- continue;
1144
- }
1145
- registrations.push({
1146
- cleanupDelayMs,
1147
- cleanupKey: createEntryCleanupKey(
1148
- type,
1149
- key,
1150
- source,
1151
- context.instanceId,
1152
- scope
1153
- ),
1154
- key,
1155
- lifecycle,
1156
- registerOptions,
1157
- scope,
1158
- source,
1159
- type,
1160
- value
1161
- });
1162
- }
1059
+ function getYouTubeStreamUrl(videoId, options = {}) {
1060
+ const params = new URLSearchParams();
1061
+ params.set("id", videoId);
1062
+ params.set("stream", options.stream || "video");
1063
+ if (options.quality) params.set("quality", options.quality);
1064
+ if (options.codec) params.set("codec", options.codec);
1065
+ return `/api/background/youtube?${params.toString()}`;
1066
+ }
1067
+ function getYouTubeThumbnailUrl(videoId, proxied = true) {
1068
+ if (proxied) {
1069
+ return `/api/background/youtube?id=${encodeURIComponent(videoId)}&stream=thumbnail`;
1163
1070
  }
1164
- return registrations;
1071
+ return `https://i.ytimg.com/vi/${encodeURIComponent(videoId)}/maxresdefault.jpg`;
1165
1072
  }
1166
- var NO_CLEANUP = () => {
1167
- };
1168
- function applyRegistryConfig(config, context) {
1169
- const modules = context.modules ?? [];
1170
- if (!config || modules.length === 0) return NO_CLEANUP;
1171
- const pageMetadata = isObject(config.registry) ? config.registry : {};
1172
- const slices = config;
1173
- const registrations = modules.flatMap((coreModule) => {
1174
- try {
1175
- return collectModuleRegistrations(
1176
- coreModule,
1177
- slices[coreModule.id],
1178
- pageMetadata,
1179
- context
1180
- );
1181
- } catch (error) {
1182
- report(`Registry apply (${coreModule.id})`, error);
1183
- return [];
1184
- }
1185
- });
1186
- if (registrations.length === 0) return NO_CLEANUP;
1187
- const cleanupScope = getCleanupScope(context);
1188
- const owner = (registration) => ({
1189
- instanceId: context.instanceId,
1190
- scope: registration.scope,
1191
- source: registration.source
1192
- });
1193
- runRegistrationBatch(context, (queue) => {
1194
- registrations.forEach(
1195
- ({ cleanupKey, key, registerOptions, source, type, value }) => {
1196
- clearCleanupTimer(cleanupScope, cleanupKey);
1197
- queue.register(type, key, value, source, registerOptions);
1073
+ function createYouTubeVideoElementProxy(controller) {
1074
+ const proxyVideo = document.createElement("video");
1075
+ Object.defineProperties(proxyVideo, {
1076
+ currentTime: {
1077
+ configurable: true,
1078
+ get: () => controller.getCurrentTime(),
1079
+ set: (value) => {
1080
+ controller.seekTo(Number(value) || 0);
1081
+ proxyVideo.dispatchEvent(new Event("timeupdate"));
1198
1082
  }
1199
- );
1200
- });
1201
- return () => {
1202
- const immediate = [];
1203
- registrations.forEach((registration) => {
1204
- if (isPersistentLifecycle(registration.lifecycle)) return;
1205
- const { cleanupDelayMs, cleanupKey, key, type } = registration;
1206
- if (typeof cleanupDelayMs === "number" && cleanupDelayMs > 0) {
1207
- scheduleCleanup(
1208
- cleanupScope,
1209
- cleanupKey,
1210
- () => context.unregister(type, key, owner(registration)),
1211
- cleanupDelayMs
1212
- );
1213
- } else {
1214
- immediate.push(registration);
1083
+ },
1084
+ duration: {
1085
+ configurable: true,
1086
+ get: () => controller.getDuration()
1087
+ },
1088
+ loop: {
1089
+ configurable: true,
1090
+ get: () => controller.getLoop(),
1091
+ set: (value) => {
1092
+ controller.setLoop(Boolean(value));
1215
1093
  }
1216
- });
1217
- if (immediate.length === 0) return;
1218
- runRegistrationBatch(context, (queue) => {
1219
- immediate.forEach(
1220
- (registration) => queue.unregister(
1221
- registration.type,
1222
- registration.key,
1223
- owner(registration)
1224
- )
1225
- );
1226
- });
1094
+ },
1095
+ muted: {
1096
+ configurable: true,
1097
+ get: () => controller.getMuted(),
1098
+ set: (value) => {
1099
+ controller.setMuted(Boolean(value));
1100
+ proxyVideo.dispatchEvent(new Event("volumechange"));
1101
+ }
1102
+ },
1103
+ pause: {
1104
+ configurable: true,
1105
+ value: () => {
1106
+ controller.pause();
1107
+ proxyVideo.dispatchEvent(new Event("pause"));
1108
+ }
1109
+ },
1110
+ paused: {
1111
+ configurable: true,
1112
+ get: () => controller.getPaused()
1113
+ },
1114
+ play: {
1115
+ configurable: true,
1116
+ value: () => controller.play().then(() => {
1117
+ proxyVideo.dispatchEvent(new Event("play"));
1118
+ proxyVideo.dispatchEvent(new Event("playing"));
1119
+ })
1120
+ },
1121
+ playbackRate: {
1122
+ configurable: true,
1123
+ get: () => controller.getPlaybackRate(),
1124
+ set: (value) => {
1125
+ controller.setPlaybackRate(Number(value) || 1);
1126
+ proxyVideo.dispatchEvent(new Event("ratechange"));
1127
+ }
1128
+ },
1129
+ volume: {
1130
+ configurable: true,
1131
+ get: () => controller.getVolume(),
1132
+ set: (value) => {
1133
+ const clamped = Math.max(0, Math.min(1, Number(value) || 0));
1134
+ controller.setVolume(clamped);
1135
+ proxyVideo.dispatchEvent(new Event("volumechange"));
1136
+ }
1137
+ }
1138
+ });
1139
+ return proxyVideo;
1140
+ }
1141
+
1142
+ // src/modules/background/utils.ts
1143
+ var WIDTH_CLASS = /^(w-|max-w-|min-w-)/;
1144
+ function resolveVideoClassNames(...inputs) {
1145
+ const tokens = inputs.flatMap(
1146
+ (input) => typeof input === "string" ? input.replace(/,/g, " ").split(/\s+/) : []
1147
+ ).filter(Boolean);
1148
+ return {
1149
+ objectStyle: Object.assign(
1150
+ {},
1151
+ ...tokens.map((token) => BG_TOKEN_TO_OBJECT_STYLE[token])
1152
+ ),
1153
+ other: tokens.filter((token) => !WIDTH_CLASS.test(token)).join(" "),
1154
+ width: tokens.filter((token) => WIDTH_CLASS.test(token)).join(" ")
1155
+ };
1156
+ }
1157
+ function resolveVideoOptions(options = {}) {
1158
+ return {
1159
+ codec: options.codec || "auto",
1160
+ corp: options.corp ?? 0,
1161
+ forceIframe: Boolean(options.forceIframe),
1162
+ isLoop: options.loop ?? false,
1163
+ isMuted: options.muted ?? true,
1164
+ playbackRate: options.playbackRate ?? 1,
1165
+ quality: options.quality || "1080p",
1166
+ shouldAutoPlay: options.autoplay ?? true,
1167
+ showPoster: Boolean(options.showPoster),
1168
+ showSpinner: options.showSpinner !== false
1227
1169
  };
1228
1170
  }
1229
-
1230
- // src/kernel/hooks.ts
1231
- function withInstanceId(instanceId, sourceOrOptions, optionsArg) {
1232
- if (typeof sourceOrOptions === "string") {
1233
- return {
1234
- optionsArg: { ...isObject(optionsArg) ? optionsArg : {}, instanceId },
1235
- sourceOrOptions
1236
- };
1171
+ function normalizeBackgroundInput(patch = {}) {
1172
+ if (!patch) return {};
1173
+ if (typeof patch === "string") {
1174
+ const trimmed = patch.trim();
1175
+ if (isYouTubeUrl(trimmed) || isDirectVideoUrl(trimmed)) {
1176
+ return { image: null, video: trimmed };
1177
+ }
1178
+ return { image: trimmed };
1237
1179
  }
1238
- if (isObject(sourceOrOptions)) {
1239
- return {
1240
- optionsArg,
1241
- sourceOrOptions: { ...sourceOrOptions, instanceId }
1242
- };
1180
+ const nextPatch = { ...patch };
1181
+ if (typeof nextPatch.image === "string" && isYouTubeUrl(nextPatch.image) && !nextPatch.video) {
1182
+ nextPatch.video = nextPatch.image;
1183
+ nextPatch.image = null;
1243
1184
  }
1185
+ return nextPatch;
1186
+ }
1187
+ function mergeBackgroundState(baseState, patch = {}) {
1188
+ const resolvedPatch = normalizeBackgroundInput(patch);
1189
+ const nextVideoOptions = {
1190
+ ...baseState.videoOptions,
1191
+ ...resolvedPatch.videoOptions || {}
1192
+ };
1193
+ const nextVideo = resolvedPatch.video !== void 0 ? resolvedPatch.video : baseState.video;
1194
+ const shouldAutoPlay = nextVideoOptions.autoplay !== false;
1195
+ const resolvedIsPlaying = resolvedPatch.isPlaying !== void 0 ? resolvedPatch.isPlaying : resolvedPatch.video !== void 0 && Boolean(nextVideo) ? shouldAutoPlay : baseState.isPlaying;
1244
1196
  return {
1245
- optionsArg: void 0,
1246
- sourceOrOptions: {
1247
- ...isObject(optionsArg) ? optionsArg : {},
1248
- instanceId
1197
+ ...baseState,
1198
+ ...resolvedPatch,
1199
+ isPlaying: resolvedIsPlaying,
1200
+ animation: resolvedPatch.animation !== void 0 ? resolvedPatch.animation ? {
1201
+ ...baseState.animation || {},
1202
+ ...resolvedPatch.animation
1203
+ } : resolvedPatch.animation : baseState.animation,
1204
+ imageStyle: {
1205
+ ...baseState.imageStyle,
1206
+ ...resolvedPatch.imageStyle || {}
1207
+ },
1208
+ noiseStyle: {
1209
+ ...baseState.noiseStyle,
1210
+ ...resolvedPatch.noiseStyle || {}
1211
+ },
1212
+ videoOptions: nextVideoOptions,
1213
+ videoStyle: {
1214
+ ...baseState.videoStyle,
1215
+ ...resolvedPatch.videoStyle || {}
1249
1216
  }
1250
1217
  };
1251
1218
  }
1252
- function withInstanceIdForUnregister(instanceId, sourceOrOptions) {
1253
- if (typeof sourceOrOptions === "string") {
1254
- return { instanceId, source: sourceOrOptions };
1255
- }
1256
- if (isObject(sourceOrOptions)) {
1257
- return { ...sourceOrOptions, instanceId };
1258
- }
1259
- return { instanceId };
1219
+ function selectPageBackground(background) {
1220
+ return background ? normalizeBackgroundInput(background) : null;
1260
1221
  }
1261
- function resolveRegisterArgsWithInstance(instanceId, sourceOrOptions, optionsArg) {
1262
- const input = withInstanceId(instanceId, sourceOrOptions, optionsArg);
1263
- return [input.sourceOrOptions, input.optionsArg];
1222
+ var generateBaseGradient = (direction, color) => {
1223
+ const to = direction === "left" ? "to right" : "to left";
1224
+ return `linear-gradient(${to}, ${color} 0%, color-mix(in srgb, ${color} 85%, transparent) 15%, color-mix(in srgb, ${color} 50%, transparent) 45%, color-mix(in srgb, ${color} 18%, transparent) 75%, transparent 100%)`;
1225
+ };
1226
+ var generateEdgeGradient = (direction, color) => {
1227
+ const to = direction === "left" ? "to right" : "to left";
1228
+ return `linear-gradient(${to}, color-mix(in srgb, ${color} 95%, transparent) 0%, color-mix(in srgb, ${color} 70%, transparent) 25%, color-mix(in srgb, ${color} 35%, transparent) 55%, color-mix(in srgb, ${color} 10%, transparent) 80%, transparent 100%)`;
1229
+ };
1230
+ function getVisualStyle(currentStyle = {}) {
1231
+ const {
1232
+ className: _className,
1233
+ leftGradient = 0,
1234
+ rightGradient = 0,
1235
+ ...baseStyle
1236
+ } = currentStyle;
1237
+ return {
1238
+ baseStyle,
1239
+ leftGradient: Number(leftGradient) || 0,
1240
+ rightGradient: Number(rightGradient) || 0
1241
+ };
1264
1242
  }
1265
- var MAX_STABILIZATION_DEPTH = 64;
1266
- function createStableFunctionEntry(fn) {
1267
- const entry = {
1268
- current: fn,
1269
- stable(...args) {
1270
- return entry.current?.apply(this, args);
1243
+ function resolveGradientSettings({
1244
+ fadeEdges = null,
1245
+ hasWidth = false,
1246
+ leftGradient = 0,
1247
+ rightGradient = 0
1248
+ }) {
1249
+ let leftPercent = 0;
1250
+ let rightPercent = 0;
1251
+ if (leftGradient > 0)
1252
+ leftPercent = Math.min(48, Math.max(12, leftGradient * 7.5));
1253
+ else if (hasWidth && fadeEdges !== false) leftPercent = 20;
1254
+ if (rightGradient > 0)
1255
+ rightPercent = Math.min(48, Math.max(12, rightGradient * 7.5));
1256
+ else if (hasWidth && fadeEdges !== false) rightPercent = 20;
1257
+ if (typeof fadeEdges === "number") {
1258
+ leftPercent = fadeEdges;
1259
+ rightPercent = fadeEdges;
1260
+ } else if (typeof fadeEdges === "string") {
1261
+ const parsed = parseFloat(fadeEdges);
1262
+ if (!Number.isNaN(parsed)) {
1263
+ leftPercent = parsed;
1264
+ rightPercent = parsed;
1271
1265
  }
1266
+ } else if (typeof fadeEdges === "object" && fadeEdges !== null) {
1267
+ const edgeObj = fadeEdges;
1268
+ if (edgeObj.left !== void 0)
1269
+ leftPercent = parseFloat(String(edgeObj.left)) || 0;
1270
+ if (edgeObj.right !== void 0)
1271
+ rightPercent = parseFloat(String(edgeObj.right)) || 0;
1272
+ } else if (fadeEdges === false) {
1273
+ leftPercent = 0;
1274
+ rightPercent = 0;
1275
+ }
1276
+ const leftOpacity = leftGradient > 0 ? Math.min(1, leftGradient * 0.22) : 0;
1277
+ const rightOpacity = rightGradient > 0 ? Math.min(1, rightGradient * 0.22) : 0;
1278
+ return {
1279
+ enabled: leftPercent > 0 || rightPercent > 0,
1280
+ leftOpacity,
1281
+ leftPercent,
1282
+ rightOpacity,
1283
+ rightPercent
1272
1284
  };
1273
- return entry;
1274
- }
1275
- function isDirectRegistryComponentPath(path, componentSlices) {
1276
- const match = /^config\.([^.[]+)\.[^.[]+$/.exec(path);
1277
- return Boolean(match && componentSlices.has(match[1]));
1278
- }
1279
- function isReactNodeLike(value) {
1280
- return value === null || value === void 0 || typeof value === "boolean" || typeof value === "string" || typeof value === "number" || isValidElement(value);
1281
- }
1282
- function isStabilizableObject(value) {
1283
- return typeof value === "object" && value !== null && !Array.isArray(value) && !isValidElement(value);
1284
1285
  }
1285
- function hasSameObjectKeys(previousValue, nextValue) {
1286
- if (!isStabilizableObject(previousValue) || !isStabilizableObject(nextValue)) {
1287
- return false;
1286
+ function generateSmoothstepStops(percent, direction = "in", color = "var(--black, #000)") {
1287
+ const steps = [
1288
+ { s: 0, t: 0 },
1289
+ { s: 0.06, t: 0.15 },
1290
+ { s: 0.22, t: 0.35 },
1291
+ { s: 0.47, t: 0.55 },
1292
+ { s: 0.76, t: 0.75 },
1293
+ { s: 0.93, t: 0.9 },
1294
+ { s: 1, t: 1 }
1295
+ ];
1296
+ if (direction === "in") {
1297
+ return steps.map(
1298
+ ({ s, t }) => s === 0 ? `transparent ${(t * percent).toFixed(1)}%` : s === 1 ? `${color} ${(t * percent).toFixed(1)}%` : `color-mix(in srgb, ${color} ${Math.round(s * 100)}%, transparent) ${(t * percent).toFixed(1)}%`
1299
+ );
1288
1300
  }
1289
- const previousKeys = Object.keys(previousValue);
1290
- const nextKeys = Object.keys(nextValue);
1291
- if (previousKeys.length !== nextKeys.length) return false;
1292
- return nextKeys.every(
1293
- (key) => Object.prototype.hasOwnProperty.call(previousValue, key)
1294
- );
1301
+ return steps.map(({ s, t }) => {
1302
+ const alpha = 1 - s;
1303
+ return alpha === 0 ? `transparent ${(100 - percent + t * percent).toFixed(1)}%` : alpha === 1 ? `${color} ${(100 - percent + t * percent).toFixed(1)}%` : `color-mix(in srgb, ${color} ${Math.round(alpha * 100)}%, transparent) ${(100 - percent + t * percent).toFixed(1)}%`;
1304
+ });
1295
1305
  }
1296
- function stabilizeRegistryValue(value, path, scope, previousValue, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
1297
- if (depth > MAX_STABILIZATION_DEPTH) return value;
1298
- if (typeof value === "function") {
1299
- const isComponent = value.name && /^[A-Z]/.test(value.name);
1300
- if (isComponent || isDirectRegistryComponentPath(path, scope.componentSlices)) {
1301
- return value;
1302
- }
1303
- scope.usedPaths.add(path);
1304
- let entry = scope.functionEntries.get(path);
1305
- if (!entry) {
1306
- entry = createStableFunctionEntry(value);
1307
- scope.functionEntries.set(path, entry);
1308
- } else {
1309
- entry.current = value;
1310
- }
1311
- return entry.stable;
1312
- }
1313
- if (value && typeof value === "object") {
1314
- if (seen.has(value)) return value;
1315
- seen.add(value);
1316
- }
1317
- if (isValidElement(value)) {
1318
- const nextProps = stabilizeRegistryValue(
1319
- value.props,
1320
- `${path}.props`,
1321
- scope,
1322
- isValidElement(previousValue) ? previousValue.props : void 0,
1323
- seen,
1324
- depth + 1
1325
- );
1326
- if (isValidElement(previousValue) && previousValue.type === value.type && previousValue.key === value.key && previousValue.props === nextProps) {
1327
- return previousValue;
1306
+ function getEdgeFadeMask({
1307
+ color = "var(--black, #000)",
1308
+ leftPercent = 0,
1309
+ rightPercent = 0
1310
+ }) {
1311
+ if (leftPercent <= 0 && rightPercent <= 0) return void 0;
1312
+ const leftStops = leftPercent > 0 ? generateSmoothstepStops(leftPercent, "in", color) : [`${color} 0%`];
1313
+ const rightStops = rightPercent > 0 ? generateSmoothstepStops(rightPercent, "out", color) : [`${color} 100%`];
1314
+ return `linear-gradient(to right, ${leftStops.join(", ")}, ${rightStops.join(", ")})`;
1315
+ }
1316
+ function applyVideoPlaybackState({
1317
+ isPlaying,
1318
+ playbackRate,
1319
+ setVideoPlaying,
1320
+ videoElement
1321
+ }) {
1322
+ if (!videoElement) return;
1323
+ const numericPlaybackRate = Number(playbackRate);
1324
+ const resolvedPlaybackRate = Number.isFinite(numericPlaybackRate) && numericPlaybackRate > 0 ? numericPlaybackRate : 1;
1325
+ try {
1326
+ if (videoElement.playbackRate !== resolvedPlaybackRate) {
1327
+ videoElement.playbackRate = resolvedPlaybackRate;
1328
1328
  }
1329
- return cloneElement(value, nextProps);
1329
+ } catch {
1330
+ return;
1330
1331
  }
1331
- if (Array.isArray(value)) {
1332
- const nextValue = value.every(isReactNodeLike) ? Children.toArray(value) : value;
1333
- const previousArray = Array.isArray(previousValue) ? previousValue : null;
1334
- let hasChanged2 = !previousArray || previousArray.length !== nextValue.length;
1335
- const stabilizedValue2 = nextValue.map((item, index) => {
1336
- const nextItem = stabilizeRegistryValue(
1337
- item,
1338
- `${path}[${index}]`,
1339
- scope,
1340
- previousArray?.[index],
1341
- seen,
1342
- depth + 1
1343
- );
1344
- if (!previousArray || nextItem !== previousArray[index]) {
1345
- hasChanged2 = true;
1346
- }
1347
- return nextItem;
1348
- });
1349
- return !hasChanged2 ? previousArray : stabilizedValue2;
1332
+ if (!isPlaying) {
1333
+ if (!videoElement.paused) videoElement.pause();
1334
+ return;
1350
1335
  }
1351
- if (typeof value !== "object" || value === null) {
1352
- return value;
1336
+ if (videoElement.readyState === 0) {
1337
+ return;
1353
1338
  }
1354
- const source = value;
1355
- const previousRecord = previousValue;
1356
- const stabilizedValue = {};
1357
- const canReusePrevious = hasSameObjectKeys(previousValue, value);
1358
- let hasChanged = !canReusePrevious;
1359
- Object.keys(source).forEach((key) => {
1360
- const nextValue = stabilizeRegistryValue(
1361
- source[key],
1362
- `${path}.${key}`,
1363
- scope,
1364
- previousRecord?.[key],
1365
- seen,
1366
- depth + 1
1367
- );
1368
- stabilizedValue[key] = nextValue;
1369
- if (!canReusePrevious || nextValue !== previousRecord?.[key]) {
1370
- hasChanged = true;
1371
- }
1372
- });
1373
- return !hasChanged ? previousValue : stabilizedValue;
1374
- }
1375
- function useAppliedRegistryConfig(payload, context, componentSlices) {
1376
- const functionEntriesRef = useRef(/* @__PURE__ */ new Map());
1377
- const appliedRef = useRef(null);
1378
- useIsomorphicLayoutEffect(() => {
1379
- const previous = appliedRef.current;
1380
- if (previous && previous.payload === payload && previous.context === context) {
1339
+ const playPromise = videoElement.play();
1340
+ if (!playPromise || typeof playPromise.then !== "function") return;
1341
+ playPromise.then(() => setVideoPlaying(true)).catch((error) => {
1342
+ if (error?.name === "AbortError" || videoElement.readyState < 2) {
1381
1343
  return;
1382
1344
  }
1383
- const functionEntries = functionEntriesRef.current;
1384
- const usedPaths = /* @__PURE__ */ new Set();
1385
- const config = stabilizeRegistryValue(
1386
- payload,
1387
- "config",
1388
- { componentSlices, functionEntries, usedPaths },
1389
- previous?.config
1390
- );
1391
- functionEntries.forEach((_entry, path) => {
1392
- if (!usedPaths.has(path)) functionEntries.delete(path);
1393
- });
1394
- if (previous && previous.config === config && previous.context === context) {
1395
- previous.payload = payload;
1345
+ if (error?.name === "NotAllowedError") {
1346
+ videoElement.muted = true;
1347
+ videoElement.play().then(() => setVideoPlaying(true)).catch(() => {
1348
+ });
1396
1349
  return;
1397
1350
  }
1398
- previous?.cleanup();
1399
- appliedRef.current = {
1400
- cleanup: applyRegistryConfig(config, context),
1401
- config,
1402
- context,
1403
- payload
1404
- };
1405
- });
1406
- useIsomorphicLayoutEffect(
1407
- () => () => {
1408
- appliedRef.current?.cleanup();
1409
- appliedRef.current = null;
1410
- },
1411
- []
1412
- );
1413
- }
1414
- function createRegistryApplyContext(actions, {
1415
- cleanupScope = /* @__PURE__ */ new Map(),
1416
- instanceId,
1417
- modules = [],
1418
- pathname
1419
- }) {
1420
- const stamp = (queue) => ({
1421
- register: (type, key, item, sourceOrOptions, optionsArg) => queue.register(
1422
- type,
1423
- key,
1424
- item,
1425
- ...resolveRegisterArgsWithInstance(
1426
- instanceId,
1427
- sourceOrOptions,
1428
- optionsArg
1429
- )
1430
- ),
1431
- unregister: (type, key, sourceOrOptions) => queue.unregister(
1432
- type,
1433
- key,
1434
- withInstanceIdForUnregister(instanceId, sourceOrOptions)
1435
- )
1351
+ setVideoPlaying(false);
1436
1352
  });
1437
- return {
1438
- ...stamp(actions),
1439
- batch: (executor) => typeof executor === "function" ? actions.batch((queue) => executor(stamp(queue))) : 0,
1440
- cleanupScope,
1441
- instanceId,
1442
- modules,
1443
- pathname
1444
- };
1445
- }
1446
- function useRegistry(config, metadata) {
1447
- const { batch, register, unregister } = useRegistryActions();
1448
- const pathname = usePathname();
1449
- const modules = useInstalledModules();
1450
- const instanceId = `registry-instance-${useId()}`;
1451
- const [cleanupScope] = useState(
1452
- () => /* @__PURE__ */ new Map()
1453
- );
1454
- const context = useMemo(
1455
- () => createRegistryApplyContext(
1456
- { batch, register, unregister },
1457
- {
1458
- cleanupScope,
1459
- instanceId,
1460
- modules,
1461
- pathname
1462
- }
1463
- ),
1464
- [batch, cleanupScope, instanceId, modules, pathname, register, unregister]
1465
- );
1466
- const payload = useMemo(
1467
- () => config ? createModulePayload(modules, config, metadata) : null,
1468
- [config, metadata, modules]
1469
- );
1470
- const componentSlices = useMemo(
1471
- () => new Set(
1472
- modules.filter(
1473
- (coreModule) => coreModule.registry?.valueKind === "component"
1474
- ).map((coreModule) => coreModule.id)
1475
- ),
1476
- [modules]
1477
- );
1478
- useAppliedRegistryConfig(payload, context, componentSlices);
1479
1353
  }
1480
- function createPageControllerStore() {
1481
- const listeners = /* @__PURE__ */ new Set();
1482
- let stack = [];
1483
- const notify = () => {
1484
- listeners.forEach((listener) => {
1485
- try {
1486
- listener();
1487
- } catch (error) {
1488
- report("Page controller listener", error);
1354
+ function triggerVideoPlaybackDirect(videoElement, nextPlaying, configuredMuted) {
1355
+ if (!videoElement) return;
1356
+ if (nextPlaying) {
1357
+ videoElement.muted = configuredMuted;
1358
+ if (!configuredMuted && videoElement.volume === 0) {
1359
+ videoElement.volume = 0.8;
1360
+ }
1361
+ videoElement.play().catch((err) => {
1362
+ if (err?.name === "NotAllowedError") {
1363
+ videoElement.muted = true;
1364
+ videoElement.play().catch(() => {
1365
+ });
1489
1366
  }
1490
1367
  });
1491
- };
1492
- return {
1493
- getSnapshot() {
1494
- const top = stack[stack.length - 1];
1495
- return top?.controllerRef?.current || null;
1496
- },
1497
- notify,
1498
- register(id, controllerRef) {
1499
- stack = stack.filter((item) => item.id !== id);
1500
- stack.push({ controllerRef, id });
1501
- notify();
1502
- },
1503
- subscribe(listener) {
1504
- listeners.add(listener);
1505
- return () => {
1506
- listeners.delete(listener);
1507
- };
1508
- },
1509
- unregister(id) {
1510
- stack = stack.filter((item) => item.id !== id);
1511
- notify();
1512
- }
1513
- };
1368
+ } else {
1369
+ videoElement.pause();
1370
+ }
1514
1371
  }
1515
- var fallbackStandaloneStore = createPageControllerStore();
1516
- getOrCreateGlobalContext(
1517
- "PageControllerContext",
1518
- null
1519
- );
1520
- getOrCreateGlobalContext(
1521
- "PageControllerStoreContext",
1522
- fallbackStandaloneStore
1523
- );
1524
- function defineModule(coreModule) {
1525
- return Object.freeze({ ...coreModule });
1372
+ function syncVideoMutedDirect(videoElement, nextMuted) {
1373
+ if (!videoElement) return;
1374
+ videoElement.muted = nextMuted;
1375
+ if (!nextMuted && videoElement.volume === 0) {
1376
+ videoElement.volume = 0.8;
1377
+ }
1526
1378
  }
1527
- function useModule(id) {
1528
- const coreModule = use(ModuleHostContext)?.byId.get(id);
1529
- return coreModule?.context ? use(coreModule.context) ?? null : null;
1379
+ function syncVideoLoopDirect(videoElement, nextLoop) {
1380
+ if (!videoElement) return;
1381
+ videoElement.loop = nextLoop;
1530
1382
  }
1531
- function definePeer(id, inert) {
1532
- const useRuntime = () => useModule(id) ?? inert;
1383
+
1384
+ // src/modules/background/motion.ts
1385
+ var DEFAULT_MOTION = Object.freeze({
1386
+ animate: Object.freeze({
1387
+ opacity: 1
1388
+ }),
1389
+ exit: Object.freeze({
1390
+ opacity: 0,
1391
+ transition: Object.freeze({
1392
+ duration: DURATION_TOKENS.SLOW,
1393
+ ease: EASING_CURVES.IN_CUBIC
1394
+ })
1395
+ }),
1396
+ initial: Object.freeze({
1397
+ opacity: 0
1398
+ }),
1399
+ transition: Object.freeze({
1400
+ delay: 0,
1401
+ duration: DURATION_TOKENS.SLOW,
1402
+ ease: EASING_CURVES.OUT_QUART
1403
+ })
1404
+ });
1405
+ function getBackgroundMotionConfig(animation) {
1533
1406
  return {
1534
- useRuntime,
1535
- useActions: () => useRuntime().actions,
1536
- useState: (selector, isEqual) => useStore(
1537
- useRuntime().store,
1538
- selector,
1539
- isEqual
1540
- )
1407
+ animate: animation?.animate ?? DEFAULT_MOTION.animate,
1408
+ exit: animation?.exit ?? DEFAULT_MOTION.exit,
1409
+ exitDurationFactor: Number(animation?.exitDurationFactor),
1410
+ initial: animation?.initial ?? DEFAULT_MOTION.initial,
1411
+ transition: animation?.transition ?? DEFAULT_MOTION.transition
1541
1412
  };
1542
1413
  }
1543
- createStore(void 0);
1414
+ function toCssDuration(seconds) {
1415
+ const value = Number(seconds);
1416
+ return `${Math.max(0, Number.isFinite(value) ? value : DURATION_TOKENS.SLOW) * 1e3}ms`;
1417
+ }
1418
+ function toCssDelay(seconds) {
1419
+ const value = Number(seconds);
1420
+ return `${Math.max(0, Number.isFinite(value) ? value : 0) * 1e3}ms`;
1421
+ }
1422
+ function toCssEasing(easing) {
1423
+ if (Array.isArray(easing)) {
1424
+ return `cubic-bezier(${easing.join(", ")})`;
1425
+ }
1426
+ if (typeof easing === "string" && easing.trim()) {
1427
+ return easing;
1428
+ }
1429
+ return "ease";
1430
+ }
1544
1431
 
1545
- // src/kernel/adapters.tsx
1546
- function useModuleRegistration(id, value, options) {
1547
- const coreModule = useInstalledModules().find(
1548
- (candidate) => candidate.id === id
1549
- );
1550
- const config = coreModule && options?.enabled !== false && value != null ? { [id]: value } : null;
1551
- useRegistry(config, pickRegistryMetadata(options));
1432
+ // src/modules/media/constants.ts
1433
+ var DEFAULT_MEDIA_STATE = Object.freeze({
1434
+ audibleElement: null,
1435
+ element: null,
1436
+ hasMedia: false,
1437
+ isPlaying: false,
1438
+ kind: null,
1439
+ loop: false,
1440
+ sourceId: null
1441
+ });
1442
+ var INERT_MEDIA_ACTIONS = Object.freeze({
1443
+ removeSource: () => {
1444
+ },
1445
+ setMuted: () => {
1446
+ },
1447
+ toggle: () => {
1448
+ },
1449
+ toggleLoop: () => {
1450
+ },
1451
+ upsertSource: () => {
1452
+ }
1453
+ });
1454
+ var MediaContext = getOrCreateGlobalContext(
1455
+ "MediaContext",
1456
+ null
1457
+ );
1458
+ createStore(DEFAULT_MEDIA_STATE);
1459
+ function useOptionalMediaActions() {
1460
+ const ctx = useContext(MediaContext);
1461
+ return ctx?.actions ?? INERT_MEDIA_ACTIONS;
1552
1462
  }
1553
1463
  var BackgroundContext = getOrCreateGlobalContext(
1554
1464
  "BackgroundContext",
1555
1465
  null
1556
1466
  );
1557
- var mediaPeer = definePeer("media", {
1558
- actions: {
1559
- removeSource: () => {
1560
- },
1561
- setMuted: () => {
1562
- },
1563
- toggle: () => {
1564
- },
1565
- toggleLoop: () => {
1566
- },
1567
- upsertSource: () => {
1568
- }
1569
- },
1570
- store: createStore({
1571
- audibleElement: null,
1572
- element: null,
1573
- hasMedia: false,
1574
- isPlaying: false,
1575
- kind: null,
1576
- loop: false,
1577
- sourceId: null
1578
- })
1579
- });
1580
1467
  function useBackgroundRegistration(config, options) {
1581
1468
  useModuleRegistration("background", config, options);
1582
1469
  }
@@ -1733,7 +1620,7 @@ function BackgroundProvider({ children }) {
1733
1620
  }
1734
1621
  };
1735
1622
  }, [overrideStore]);
1736
- const media = mediaPeer.useActions();
1623
+ const media = useOptionalMediaActions();
1737
1624
  const { isPlaying, isVideo, videoElement } = state;
1738
1625
  const isLoop = Boolean(state.videoOptions?.loop);
1739
1626
  useEffect(() => {
@@ -1782,6 +1669,21 @@ function useBackground(config, options) {
1782
1669
  const state = useBackgroundState();
1783
1670
  return useMemo(() => ({ ...state, ...actions }), [actions, state]);
1784
1671
  }
1672
+ var NOOP_BACKGROUND_STORE = createStore(
1673
+ DEFAULT_BACKGROUND_COMPUTED
1674
+ );
1675
+ function useOptionalBackgroundState(selector, isEqual) {
1676
+ const ctx = useContext(BackgroundContext);
1677
+ return useStore(
1678
+ ctx?.store ?? NOOP_BACKGROUND_STORE,
1679
+ selector,
1680
+ isEqual
1681
+ );
1682
+ }
1683
+ function useOptionalBackgroundActions() {
1684
+ const ctx = useContext(BackgroundContext);
1685
+ return ctx?.actions ?? INERT_BACKGROUND_ACTIONS;
1686
+ }
1785
1687
  var PLAYER_STATE = { ENDED: 0, PLAYING: 1, PAUSED: 2, BUFFERING: 3 };
1786
1688
  var apiPromise = null;
1787
1689
  function loadYouTubeIframeApi() {
@@ -3190,6 +3092,6 @@ function defineBackground(definition = {}) {
3190
3092
  });
3191
3093
  }
3192
3094
 
3193
- export { BACKGROUND_MEDIA_ID, BackgroundContext, BackgroundOverlay, BackgroundProvider, DEFAULT_BACKGROUND, backgroundModule, backgroundTheme, defineBackground, extractYouTubeVideoId, getYouTubeStreamUrl, getYouTubeThumbnailUrl, isDirectVideoUrl, isYouTubeUrl, parseYouTubeUrlConfig, useBackground, useBackgroundActions, useBackgroundRegistration, useBackgroundState };
3095
+ export { BACKGROUND_MEDIA_ID, BackgroundContext, BackgroundOverlay, BackgroundProvider, DEFAULT_BACKGROUND, DEFAULT_BACKGROUND_COMPUTED, INERT_BACKGROUND_ACTIONS, backgroundModule, backgroundTheme, defineBackground, extractYouTubeVideoId, getYouTubeStreamUrl, getYouTubeThumbnailUrl, isDirectVideoUrl, isYouTubeUrl, parseYouTubeUrlConfig, useBackground, useBackgroundActions, useBackgroundRegistration, useBackgroundState, useOptionalBackgroundActions, useOptionalBackgroundState };
3194
3096
  //# sourceMappingURL=index.js.map
3195
3097
  //# sourceMappingURL=index.js.map