@almadar/ui 5.76.2 → 5.76.5

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.
@@ -620,14 +620,17 @@ function lazyFamilyIcon(libKey, importer, pick, fallbackName, family) {
620
620
  Wrapped.displayName = `Lazy.${libKey}.${fallbackName}`;
621
621
  return Wrapped;
622
622
  }
623
+ function isComponentLike(v) {
624
+ return v != null && (typeof v === "function" || typeof v === "object");
625
+ }
623
626
  function resolveLucide(name) {
624
627
  if (lucideAliases[name]) return lucideAliases[name];
625
628
  const pascal = kebabToPascal(name);
626
629
  const lucideMap = LucideIcons2__namespace;
627
630
  const direct = lucideMap[pascal];
628
- if (direct && typeof direct === "function") return direct;
631
+ if (isComponentLike(direct)) return direct;
629
632
  const asIs = lucideMap[name];
630
- if (asIs && typeof asIs === "function") return asIs;
633
+ if (isComponentLike(asIs)) return asIs;
631
634
  return LucideIcons2__namespace.HelpCircle;
632
635
  }
633
636
  function resolvePhosphor(name, weight, family) {
@@ -1271,12 +1274,13 @@ function resolveIcon(name) {
1271
1274
  }
1272
1275
  function doResolve(name) {
1273
1276
  if (iconAliases[name]) return iconAliases[name];
1277
+ const isComponentLike2 = (v) => v != null && (typeof v === "function" || typeof v === "object");
1274
1278
  const pascalName = kebabToPascal2(name);
1275
1279
  const lucideMap = LucideIcons2__namespace;
1276
1280
  const directLookup = lucideMap[pascalName];
1277
- if (directLookup && typeof directLookup === "object") return directLookup;
1281
+ if (isComponentLike2(directLookup)) return directLookup;
1278
1282
  const asIs = lucideMap[name];
1279
- if (asIs && typeof asIs === "object") return asIs;
1283
+ if (isComponentLike2(asIs)) return asIs;
1280
1284
  return LucideIcons2__namespace.HelpCircle;
1281
1285
  }
1282
1286
  var colorTokenClasses, iconAliases, resolvedCache, sizeClasses, animationClasses, Icon;
@@ -12145,14 +12149,318 @@ var init_useCanvasEffects = __esm({
12145
12149
  "use client";
12146
12150
  }
12147
12151
  });
12152
+ function pickPath(entry) {
12153
+ if (Array.isArray(entry.path)) {
12154
+ return entry.path[Math.floor(Math.random() * entry.path.length)];
12155
+ }
12156
+ return entry.path;
12157
+ }
12158
+ function useGameAudio({
12159
+ manifest,
12160
+ baseUrl = "",
12161
+ initialMuted = false,
12162
+ initialVolume = 1
12163
+ }) {
12164
+ const [muted, setMutedState] = React107.useState(initialMuted);
12165
+ const [masterVolume, setMasterVolumeState] = React107.useState(initialVolume);
12166
+ const mutedRef = React107.useRef(muted);
12167
+ const volumeRef = React107.useRef(masterVolume);
12168
+ const manifestRef = React107.useRef(manifest);
12169
+ mutedRef.current = muted;
12170
+ volumeRef.current = masterVolume;
12171
+ manifestRef.current = manifest;
12172
+ const poolsRef = React107.useRef(/* @__PURE__ */ new Map());
12173
+ const getOrCreateElement = React107.useCallback((key) => {
12174
+ const entry = manifestRef.current[key];
12175
+ if (!entry) return null;
12176
+ let pool = poolsRef.current.get(key);
12177
+ if (!pool) {
12178
+ pool = [];
12179
+ poolsRef.current.set(key, pool);
12180
+ }
12181
+ const maxSize = entry.poolSize ?? 1;
12182
+ for (const audio of pool) {
12183
+ if (audio.paused && (audio.ended || audio.currentTime === 0)) {
12184
+ return audio;
12185
+ }
12186
+ }
12187
+ if (pool.length < maxSize) {
12188
+ const src = baseUrl + pickPath(entry);
12189
+ const audio = new Audio(src);
12190
+ audio.loop = entry.loop ?? false;
12191
+ pool.push(audio);
12192
+ return audio;
12193
+ }
12194
+ if (!entry.loop) {
12195
+ let oldest = pool[0];
12196
+ for (const audio of pool) {
12197
+ if (audio.currentTime > oldest.currentTime) {
12198
+ oldest = audio;
12199
+ }
12200
+ }
12201
+ oldest.pause();
12202
+ oldest.currentTime = 0;
12203
+ return oldest;
12204
+ }
12205
+ return null;
12206
+ }, [baseUrl]);
12207
+ const play = React107.useCallback((key) => {
12208
+ if (mutedRef.current) return;
12209
+ const entry = manifestRef.current[key];
12210
+ if (!entry) return;
12211
+ const audio = getOrCreateElement(key);
12212
+ if (!audio) return;
12213
+ audio.volume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
12214
+ if (!entry.loop) {
12215
+ audio.currentTime = 0;
12216
+ }
12217
+ const promise = audio.play();
12218
+ if (promise) {
12219
+ promise.catch(() => {
12220
+ });
12221
+ }
12222
+ }, [getOrCreateElement]);
12223
+ const stop = React107.useCallback((key) => {
12224
+ const pool = poolsRef.current.get(key);
12225
+ if (!pool) return;
12226
+ for (const audio of pool) {
12227
+ audio.pause();
12228
+ audio.currentTime = 0;
12229
+ }
12230
+ }, []);
12231
+ const currentMusicKeyRef = React107.useRef(null);
12232
+ const currentMusicElRef = React107.useRef(null);
12233
+ const musicFadeRef = React107.useRef(null);
12234
+ const pendingMusicKeyRef = React107.useRef(null);
12235
+ const clearMusicFade = React107.useCallback(() => {
12236
+ if (musicFadeRef.current) {
12237
+ clearInterval(musicFadeRef.current);
12238
+ musicFadeRef.current = null;
12239
+ }
12240
+ }, []);
12241
+ const playMusic = React107.useCallback((key) => {
12242
+ if (key === currentMusicKeyRef.current) return;
12243
+ pendingMusicKeyRef.current = key;
12244
+ const entry = manifestRef.current[key];
12245
+ if (!entry) return;
12246
+ const fadeDurationMs = entry.crossfadeDurationMs ?? 1500;
12247
+ const stepMs = 50;
12248
+ const totalSteps = Math.max(1, fadeDurationMs / stepMs);
12249
+ const targetVolume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
12250
+ clearMusicFade();
12251
+ const src = baseUrl + (Array.isArray(entry.path) ? entry.path[0] : entry.path);
12252
+ const incoming = new Audio(src);
12253
+ incoming.loop = true;
12254
+ incoming.volume = 0;
12255
+ const outgoing = currentMusicElRef.current;
12256
+ const outgoingStartVol = outgoing?.volume ?? 0;
12257
+ currentMusicKeyRef.current = key;
12258
+ currentMusicElRef.current = incoming;
12259
+ if (!mutedRef.current) {
12260
+ incoming.play().catch(() => {
12261
+ currentMusicKeyRef.current = null;
12262
+ currentMusicElRef.current = outgoing;
12263
+ });
12264
+ }
12265
+ let step = 0;
12266
+ musicFadeRef.current = setInterval(() => {
12267
+ step++;
12268
+ const progress = Math.min(step / totalSteps, 1);
12269
+ incoming.volume = Math.min(1, targetVolume * progress);
12270
+ if (outgoing) {
12271
+ outgoing.volume = Math.max(0, outgoingStartVol * (1 - progress));
12272
+ }
12273
+ if (progress >= 1) {
12274
+ clearMusicFade();
12275
+ if (outgoing) {
12276
+ outgoing.pause();
12277
+ outgoing.src = "";
12278
+ }
12279
+ }
12280
+ }, stepMs);
12281
+ }, [baseUrl, clearMusicFade]);
12282
+ const stopMusic = React107.useCallback((fadeDurationMs = 1e3) => {
12283
+ const outgoing = currentMusicElRef.current;
12284
+ if (!outgoing) return;
12285
+ currentMusicKeyRef.current = null;
12286
+ currentMusicElRef.current = null;
12287
+ pendingMusicKeyRef.current = null;
12288
+ clearMusicFade();
12289
+ const startVolume = outgoing.volume;
12290
+ const stepMs = 50;
12291
+ const totalSteps = Math.max(1, fadeDurationMs / stepMs);
12292
+ let step = 0;
12293
+ musicFadeRef.current = setInterval(() => {
12294
+ step++;
12295
+ const progress = step / totalSteps;
12296
+ outgoing.volume = Math.max(0, startVolume * (1 - progress));
12297
+ if (progress >= 1) {
12298
+ clearMusicFade();
12299
+ outgoing.pause();
12300
+ outgoing.src = "";
12301
+ }
12302
+ }, stepMs);
12303
+ }, [clearMusicFade]);
12304
+ const stopAll = React107.useCallback(() => {
12305
+ for (const pool of poolsRef.current.values()) {
12306
+ for (const audio of pool) {
12307
+ audio.pause();
12308
+ audio.currentTime = 0;
12309
+ }
12310
+ }
12311
+ stopMusic(0);
12312
+ }, [stopMusic]);
12313
+ const setMuted = React107.useCallback((value) => {
12314
+ setMutedState(value);
12315
+ if (value) {
12316
+ for (const [key, pool] of poolsRef.current.entries()) {
12317
+ if (manifestRef.current[key]?.loop) {
12318
+ for (const audio of pool) {
12319
+ if (!audio.paused) audio.pause();
12320
+ }
12321
+ }
12322
+ }
12323
+ currentMusicElRef.current?.pause();
12324
+ } else {
12325
+ for (const [key, pool] of poolsRef.current.entries()) {
12326
+ const entry = manifestRef.current[key];
12327
+ if (entry?.loop && entry?.autostart) {
12328
+ for (const audio of pool) {
12329
+ if (audio.paused) audio.play().catch(() => {
12330
+ });
12331
+ }
12332
+ }
12333
+ }
12334
+ const musicEl = currentMusicElRef.current;
12335
+ if (musicEl) {
12336
+ musicEl.play().catch(() => {
12337
+ });
12338
+ }
12339
+ }
12340
+ }, []);
12341
+ const setMasterVolume = React107.useCallback((volume) => {
12342
+ const clamped = Math.max(0, Math.min(1, volume));
12343
+ setMasterVolumeState(clamped);
12344
+ for (const [key, pool] of poolsRef.current.entries()) {
12345
+ const entryVol = manifestRef.current[key]?.volume ?? 1;
12346
+ for (const audio of pool) {
12347
+ audio.volume = Math.min(1, entryVol * clamped);
12348
+ }
12349
+ }
12350
+ if (!musicFadeRef.current && currentMusicElRef.current) {
12351
+ const key = currentMusicKeyRef.current;
12352
+ const entryVol = key ? manifestRef.current[key]?.volume ?? 1 : 1;
12353
+ currentMusicElRef.current.volume = Math.min(1, entryVol * clamped);
12354
+ }
12355
+ }, []);
12356
+ const unlockedRef = React107.useRef(false);
12357
+ React107.useEffect(() => {
12358
+ const autoKeys = Object.keys(manifest).filter((k) => manifest[k].autostart);
12359
+ const hasPendingMusic = () => pendingMusicKeyRef.current !== null;
12360
+ const hasAutoStart = autoKeys.length > 0;
12361
+ if (!hasAutoStart && !hasPendingMusic()) return;
12362
+ const unlock = () => {
12363
+ if (unlockedRef.current) return;
12364
+ unlockedRef.current = true;
12365
+ if (!mutedRef.current) {
12366
+ for (const key of autoKeys) {
12367
+ play(key);
12368
+ }
12369
+ const pending = pendingMusicKeyRef.current;
12370
+ if (pending && pending !== currentMusicKeyRef.current) {
12371
+ playMusic(pending);
12372
+ }
12373
+ }
12374
+ };
12375
+ document.addEventListener("click", unlock, { once: true });
12376
+ document.addEventListener("keydown", unlock, { once: true });
12377
+ document.addEventListener("touchstart", unlock, { once: true });
12378
+ return () => {
12379
+ document.removeEventListener("click", unlock);
12380
+ document.removeEventListener("keydown", unlock);
12381
+ document.removeEventListener("touchstart", unlock);
12382
+ };
12383
+ }, [manifest, play, playMusic]);
12384
+ React107.useEffect(() => {
12385
+ return () => {
12386
+ clearMusicFade();
12387
+ for (const pool of poolsRef.current.values()) {
12388
+ for (const audio of pool) {
12389
+ audio.pause();
12390
+ audio.src = "";
12391
+ }
12392
+ }
12393
+ poolsRef.current.clear();
12394
+ if (currentMusicElRef.current) {
12395
+ currentMusicElRef.current.pause();
12396
+ currentMusicElRef.current.src = "";
12397
+ currentMusicElRef.current = null;
12398
+ }
12399
+ };
12400
+ }, [clearMusicFade]);
12401
+ return {
12402
+ play,
12403
+ stop,
12404
+ stopAll,
12405
+ playMusic,
12406
+ stopMusic,
12407
+ muted,
12408
+ setMuted,
12409
+ masterVolume,
12410
+ setMasterVolume
12411
+ };
12412
+ }
12148
12413
  var init_useGameAudio = __esm({
12149
12414
  "components/game/shared/hooks/useGameAudio.ts"() {
12150
12415
  "use client";
12416
+ useGameAudio.displayName = "useGameAudio";
12151
12417
  }
12152
12418
  });
12153
12419
  function useGameAudioContextOptional() {
12154
12420
  return React107.useContext(GameAudioContext);
12155
12421
  }
12422
+ function GameAudioProvider({
12423
+ manifest,
12424
+ baseUrl = "",
12425
+ children,
12426
+ initialMuted = false
12427
+ }) {
12428
+ const eventBus = useEventBus();
12429
+ const { play, stop, stopAll, playMusic, stopMusic, muted, setMuted, masterVolume, setMasterVolume } = useGameAudio({ manifest, baseUrl, initialMuted });
12430
+ React107.useEffect(() => {
12431
+ const unsubPlay = eventBus.on("UI:PLAY_SOUND", (event) => {
12432
+ const key = event.payload?.key;
12433
+ if (key) play(key);
12434
+ });
12435
+ const unsubStop = eventBus.on("UI:STOP_SOUND", (event) => {
12436
+ const key = event.payload?.key;
12437
+ if (key) {
12438
+ stop(key);
12439
+ } else {
12440
+ stopAll();
12441
+ }
12442
+ });
12443
+ const unsubChangeMusic = eventBus.on("UI:CHANGE_MUSIC", (event) => {
12444
+ const key = event.payload?.key;
12445
+ if (key) {
12446
+ playMusic(key);
12447
+ } else {
12448
+ stopMusic();
12449
+ }
12450
+ });
12451
+ const unsubStopMusic = eventBus.on("UI:STOP_MUSIC", () => {
12452
+ stopMusic();
12453
+ });
12454
+ return () => {
12455
+ unsubPlay();
12456
+ unsubStop();
12457
+ unsubChangeMusic();
12458
+ unsubStopMusic();
12459
+ };
12460
+ }, [eventBus, play, stop, stopAll, playMusic, stopMusic]);
12461
+ const value = { muted, setMuted, masterVolume, setMasterVolume, play, playMusic, stopMusic };
12462
+ return /* @__PURE__ */ jsxRuntime.jsx(GameAudioContext.Provider, { value, children });
12463
+ }
12156
12464
  var GameAudioContext;
12157
12465
  var init_GameAudioProvider = __esm({
12158
12466
  "components/game/shared/providers/GameAudioProvider.tsx"() {
@@ -12161,6 +12469,7 @@ var init_GameAudioProvider = __esm({
12161
12469
  init_useGameAudio();
12162
12470
  GameAudioContext = React107.createContext(null);
12163
12471
  GameAudioContext.displayName = "GameAudioContext";
12472
+ GameAudioProvider.displayName = "GameAudioProvider";
12164
12473
  }
12165
12474
  });
12166
12475
  function GameAudioToggle({
@@ -50801,9 +51110,33 @@ var init_ToastSlot = __esm({
50801
51110
  ToastSlot.displayName = "ToastSlot";
50802
51111
  }
50803
51112
  });
50804
-
50805
- // components/core/organisms/component-registry.generated.ts
50806
- var COMPONENT_REGISTRY;
51113
+ function lazyThree(name, loader) {
51114
+ const Lazy = React107__namespace.default.lazy(
51115
+ () => loader().then((m) => {
51116
+ const Resolved = m[name];
51117
+ if (!Resolved) {
51118
+ throw new Error(
51119
+ `[@almadar/ui] 3D component "${name}" was not found in the three subpath bundle.`
51120
+ );
51121
+ }
51122
+ return { default: Resolved };
51123
+ })
51124
+ );
51125
+ function ThreeWrapper(props) {
51126
+ return React107__namespace.default.createElement(
51127
+ ThreeBoundary,
51128
+ { name },
51129
+ React107__namespace.default.createElement(
51130
+ React107__namespace.default.Suspense,
51131
+ { fallback: null },
51132
+ React107__namespace.default.createElement(Lazy, props)
51133
+ )
51134
+ );
51135
+ }
51136
+ ThreeWrapper.displayName = `Lazy(${name})`;
51137
+ return ThreeWrapper;
51138
+ }
51139
+ var ThreeBoundary, GameBoard3D, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
50807
51140
  var init_component_registry_generated = __esm({
50808
51141
  "components/core/organisms/component-registry.generated.ts"() {
50809
51142
  init_AboutPageTemplate();
@@ -50914,9 +51247,11 @@ var init_component_registry_generated = __esm({
50914
51247
  init_Form();
50915
51248
  init_FormField();
50916
51249
  init_FormSectionHeader();
51250
+ init_GameAudioProvider();
50917
51251
  init_GameAudioToggle();
50918
51252
  init_GameCard();
50919
51253
  init_GameHud();
51254
+ init_GameIcon();
50920
51255
  init_GameMenu();
50921
51256
  init_GameShell();
50922
51257
  init_GameTemplate();
@@ -51101,6 +51436,33 @@ var init_component_registry_generated = __esm({
51101
51436
  init_WizardProgress();
51102
51437
  init_WorldMapBoard();
51103
51438
  init_WorldMapTemplate();
51439
+ ThreeBoundary = class extends React107__namespace.default.Component {
51440
+ constructor() {
51441
+ super(...arguments);
51442
+ __publicField(this, "state", { failed: false });
51443
+ }
51444
+ static getDerivedStateFromError() {
51445
+ return { failed: true };
51446
+ }
51447
+ render() {
51448
+ if (this.state.failed) {
51449
+ return React107__namespace.default.createElement(
51450
+ "div",
51451
+ {
51452
+ "data-testid": "three-unavailable",
51453
+ style: { padding: 16, fontSize: 13, lineHeight: 1.5, opacity: 0.7 }
51454
+ },
51455
+ `3D pattern "${this.props.name}" requires three.js. Install the optional peers three + @react-three/fiber + @react-three/drei (matching the host React major) to render it.`
51456
+ );
51457
+ }
51458
+ return this.props.children;
51459
+ }
51460
+ };
51461
+ GameBoard3D = lazyThree("GameBoard3D", () => import('@almadar/ui/components/molecules/game/three'));
51462
+ GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
51463
+ GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
51464
+ GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
51465
+ GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
51104
51466
  COMPONENT_REGISTRY = {
51105
51467
  "AboutPageTemplate": AboutPageTemplate,
51106
51468
  "Accordion": Accordion,
@@ -51216,9 +51578,16 @@ var init_component_registry_generated = __esm({
51216
51578
  "Form": Form,
51217
51579
  "FormField": FormField,
51218
51580
  "FormSectionHeader": FormSectionHeader,
51581
+ "GameAudioProvider": GameAudioProvider,
51219
51582
  "GameAudioToggle": GameAudioToggle,
51583
+ "GameBoard3D": GameBoard3D,
51584
+ "GameCanvas3D": GameCanvas3D,
51585
+ "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
51586
+ "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
51587
+ "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
51220
51588
  "GameCard": GameCard,
51221
51589
  "GameHud": GameHud,
51590
+ "GameIcon": GameIcon,
51222
51591
  "GameMenu": GameMenu,
51223
51592
  "GameShell": GameShell,
51224
51593
  "GameTemplate": GameTemplate,