stormcloud-video-player 0.2.8 → 0.2.10

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.
@@ -76,9 +76,22 @@ function createImaController(video, options) {
76
76
  'script[data-ima="true"]'
77
77
  );
78
78
  if (existing) {
79
- return new Promise(
80
- (resolve) => existing.addEventListener("load", () => resolve())
81
- );
79
+ if (window.google?.ima) {
80
+ return Promise.resolve();
81
+ }
82
+ return new Promise((resolve, reject) => {
83
+ const timeout = setTimeout(() => {
84
+ reject(new Error("IMA SDK load timeout"));
85
+ }, 1e4);
86
+ existing.addEventListener("load", () => {
87
+ clearTimeout(timeout);
88
+ resolve();
89
+ });
90
+ existing.addEventListener("error", () => {
91
+ clearTimeout(timeout);
92
+ reject(new Error("IMA SDK load failed"));
93
+ });
94
+ });
82
95
  }
83
96
  return new Promise((resolve, reject) => {
84
97
  const script = document.createElement("script");
@@ -107,6 +120,17 @@ function createImaController(video, options) {
107
120
  adsRequest.adTagUrl = vastTagUrl;
108
121
  adsLoader.requestAds(adsRequest);
109
122
  }
123
+ function destroyAdsManager() {
124
+ if (adsManager) {
125
+ try {
126
+ console.log("[IMA] Destroying existing ads manager");
127
+ adsManager.destroy();
128
+ } catch (error) {
129
+ console.warn("[IMA] Error destroying ads manager:", error);
130
+ }
131
+ adsManager = void 0;
132
+ }
133
+ }
110
134
  return {
111
135
  initialize() {
112
136
  ensureImaLoaded().then(() => {
@@ -139,9 +163,22 @@ function createImaController(video, options) {
139
163
  },
140
164
  async requestAds(vastTagUrl) {
141
165
  console.log("[IMA] Requesting ads:", vastTagUrl);
166
+ if (adPlaying) {
167
+ console.warn(
168
+ "[IMA] Cannot request new ads while an ad is playing. Call stop() first."
169
+ );
170
+ return Promise.reject(
171
+ new Error("Ad already playing - cannot request new ads")
172
+ );
173
+ }
174
+ destroyAdsManager();
175
+ adsLoadedReject = void 0;
176
+ adsLoadedResolve = void 0;
177
+ let currentReject;
142
178
  adsLoadedPromise = new Promise((resolve, reject) => {
143
179
  adsLoadedResolve = resolve;
144
180
  adsLoadedReject = reject;
181
+ currentReject = reject;
145
182
  setTimeout(() => {
146
183
  if (adsLoadedReject) {
147
184
  adsLoadedReject(new Error("Ad request timeout"));
@@ -163,7 +200,7 @@ function createImaController(video, options) {
163
200
  container.style.top = "0";
164
201
  container.style.right = "0";
165
202
  container.style.bottom = "0";
166
- container.style.display = "flex";
203
+ container.style.display = "none";
167
204
  container.style.alignItems = "center";
168
205
  container.style.justifyContent = "center";
169
206
  container.style.pointerEvents = "none";
@@ -203,14 +240,14 @@ function createImaController(video, options) {
203
240
  AdErrorEvent.AD_ERROR,
204
241
  (errorEvent) => {
205
242
  console.error("[IMA] Ad error:", errorEvent.getError());
206
- try {
207
- adsManager?.destroy?.();
208
- } catch {
209
- }
243
+ destroyAdsManager();
210
244
  adPlaying = false;
211
245
  video.muted = originalMutedState;
212
- if (adContainerEl)
246
+ if (adContainerEl) {
213
247
  adContainerEl.style.pointerEvents = "none";
248
+ adContainerEl.style.display = "none";
249
+ console.log("[IMA] Ad container hidden after error");
250
+ }
214
251
  if (adsLoadedReject) {
215
252
  adsLoadedReject(new Error("Ad playback error"));
216
253
  adsLoadedReject = void 0;
@@ -243,8 +280,6 @@ function createImaController(video, options) {
243
280
  AdEvent.CONTENT_PAUSE_REQUESTED,
244
281
  () => {
245
282
  console.log("[IMA] Content pause requested");
246
- originalMutedState = video.muted;
247
- video.muted = true;
248
283
  if (!options?.continueLiveStreamDuringAds) {
249
284
  video.pause();
250
285
  console.log("[IMA] Video paused (VOD mode)");
@@ -253,20 +288,34 @@ function createImaController(video, options) {
253
288
  "[IMA] Video continues playing but muted (Live mode)"
254
289
  );
255
290
  }
291
+ video.muted = true;
256
292
  adPlaying = true;
257
- if (adContainerEl)
258
- adContainerEl.style.pointerEvents = "auto";
259
293
  emit("content_pause");
260
294
  }
261
295
  );
296
+ adsManager.addEventListener(AdEvent.STARTED, () => {
297
+ console.log("[IMA] Ad started playing");
298
+ if (adContainerEl) {
299
+ adContainerEl.style.pointerEvents = "auto";
300
+ adContainerEl.style.display = "flex";
301
+ console.log(
302
+ "[IMA] Ad container visibility set to flex with pointer events enabled"
303
+ );
304
+ }
305
+ });
262
306
  adsManager.addEventListener(
263
307
  AdEvent.CONTENT_RESUME_REQUESTED,
264
308
  () => {
265
309
  console.log("[IMA] Content resume requested");
266
310
  adPlaying = false;
267
311
  video.muted = originalMutedState;
268
- if (adContainerEl)
312
+ if (adContainerEl) {
269
313
  adContainerEl.style.pointerEvents = "none";
314
+ adContainerEl.style.display = "none";
315
+ console.log(
316
+ "[IMA] Ad container hidden - pointer events disabled"
317
+ );
318
+ }
270
319
  if (!options?.continueLiveStreamDuringAds) {
271
320
  video.play()?.catch(() => {
272
321
  });
@@ -283,7 +332,13 @@ function createImaController(video, options) {
283
332
  console.log("[IMA] All ads completed");
284
333
  adPlaying = false;
285
334
  video.muted = originalMutedState;
286
- if (adContainerEl) adContainerEl.style.pointerEvents = "none";
335
+ if (adContainerEl) {
336
+ adContainerEl.style.pointerEvents = "none";
337
+ adContainerEl.style.display = "none";
338
+ console.log(
339
+ "[IMA] Ad container hidden after all ads completed"
340
+ );
341
+ }
287
342
  if (!options?.continueLiveStreamDuringAds) {
288
343
  video.play().catch(() => {
289
344
  });
@@ -326,6 +381,13 @@ function createImaController(video, options) {
326
381
  google.ima.AdErrorEvent.Type.AD_ERROR,
327
382
  (adErrorEvent) => {
328
383
  console.error("[IMA] Ads loader error:", adErrorEvent.getError());
384
+ adPlaying = false;
385
+ video.muted = originalMutedState;
386
+ if (adContainerEl) adContainerEl.style.pointerEvents = "none";
387
+ if (!options?.continueLiveStreamDuringAds) {
388
+ video.play().catch(() => {
389
+ });
390
+ }
329
391
  if (adsLoadedReject) {
330
392
  adsLoadedReject(new Error("Ads loader error"));
331
393
  adsLoadedReject = void 0;
@@ -341,11 +403,9 @@ function createImaController(video, options) {
341
403
  return adsLoadedPromise;
342
404
  } catch (error) {
343
405
  console.error("[IMA] Failed to request ads:", error);
344
- if (adsLoadedReject) {
345
- adsLoadedReject(error);
346
- adsLoadedReject = void 0;
347
- adsLoadedResolve = void 0;
348
- }
406
+ currentReject?.(error);
407
+ adsLoadedReject = void 0;
408
+ adsLoadedResolve = void 0;
349
409
  return Promise.reject(error);
350
410
  }
351
411
  },
@@ -365,14 +425,6 @@ function createImaController(video, options) {
365
425
  const height = video.clientHeight || 360;
366
426
  console.log(`[IMA] Initializing ads manager (${width}x${height})`);
367
427
  adsManager.init(width, height, window.google.ima.ViewMode.NORMAL);
368
- if (!options?.continueLiveStreamDuringAds) {
369
- console.log("[IMA] Pausing video for ad playback (VOD mode)");
370
- video.pause();
371
- } else {
372
- console.log(
373
- "[IMA] Keeping video playing but muted for ad playback (Live mode)"
374
- );
375
- }
376
428
  adPlaying = true;
377
429
  console.log("[IMA] Starting ad playback");
378
430
  adsManager.start();
@@ -390,10 +442,16 @@ function createImaController(video, options) {
390
442
  async stop() {
391
443
  adPlaying = false;
392
444
  video.muted = originalMutedState;
445
+ if (adContainerEl) {
446
+ adContainerEl.style.pointerEvents = "none";
447
+ adContainerEl.style.display = "none";
448
+ console.log("[IMA] Ad container hidden after stop");
449
+ }
393
450
  try {
394
451
  adsManager?.stop?.();
395
452
  } catch {
396
453
  }
454
+ destroyAdsManager();
397
455
  if (!options?.continueLiveStreamDuringAds) {
398
456
  video.play().catch(() => {
399
457
  });
@@ -403,12 +461,13 @@ function createImaController(video, options) {
403
461
  }
404
462
  },
405
463
  destroy() {
406
- try {
407
- adsManager?.destroy?.();
408
- } catch {
409
- }
464
+ destroyAdsManager();
410
465
  adPlaying = false;
411
466
  video.muted = originalMutedState;
467
+ if (adContainerEl) {
468
+ adContainerEl.style.pointerEvents = "none";
469
+ adContainerEl.style.display = "none";
470
+ }
412
471
  try {
413
472
  adsLoader?.destroy?.();
414
473
  } catch {
@@ -416,6 +475,9 @@ function createImaController(video, options) {
416
475
  if (adContainerEl?.parentElement) {
417
476
  adContainerEl.parentElement.removeChild(adContainerEl);
418
477
  }
478
+ adContainerEl = void 0;
479
+ adDisplayContainer = void 0;
480
+ adsLoader = void 0;
419
481
  },
420
482
  isAdPlaying() {
421
483
  return adPlaying;
@@ -471,6 +533,7 @@ function createImaController(video, options) {
471
533
  }
472
534
 
473
535
  // src/utils/tracking.ts
536
+ var cachedBrowserId = null;
474
537
  function getClientInfo() {
475
538
  const ua = navigator.userAgent;
476
539
  const platform = navigator.platform;
@@ -613,6 +676,9 @@ function getClientInfo() {
613
676
  };
614
677
  }
615
678
  async function getBrowserID(clientInfo) {
679
+ if (cachedBrowserId) {
680
+ return cachedBrowserId;
681
+ }
616
682
  const fingerprintString = JSON.stringify(clientInfo);
617
683
  if (typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest) {
618
684
  try {
@@ -623,6 +689,7 @@ async function getBrowserID(clientInfo) {
623
689
  );
624
690
  const hashArray = Array.from(new Uint8Array(hashBuffer));
625
691
  const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
692
+ cachedBrowserId = hashHex;
626
693
  return hashHex;
627
694
  } catch (error) {
628
695
  console.warn(
@@ -639,7 +706,8 @@ async function getBrowserID(clientInfo) {
639
706
  const fallbackHash = Math.abs(hash).toString(16).padStart(8, "0");
640
707
  const timestamp = Date.now().toString(16).padStart(12, "0");
641
708
  const random = Math.random().toString(16).substring(2, 14).padStart(12, "0");
642
- return (fallbackHash + timestamp + random).padEnd(64, "0");
709
+ cachedBrowserId = (fallbackHash + timestamp + random).padEnd(64, "0");
710
+ return cachedBrowserId;
643
711
  }
644
712
  async function sendInitialTracking(licenseKey) {
645
713
  try {
@@ -887,17 +955,8 @@ var StormcloudVideoPlayer = class {
887
955
  this.video.muted = !!this.config.muted;
888
956
  this.ima.initialize();
889
957
  this.ima.on("all_ads_completed", () => {
890
- if (!this.inAdBreak) return;
891
- const remaining = this.getRemainingAdMs();
892
- if (remaining > 500 && this.adPodQueue.length > 0) {
893
- const next = this.adPodQueue.shift();
894
- this.currentAdIndex++;
895
- this.playSingleAd(next).catch(() => {
896
- });
897
- } else {
898
- this.currentAdIndex = 0;
899
- this.totalAdsInBreak = 0;
900
- this.showAds = false;
958
+ if (this.config.debugAdTiming) {
959
+ console.log("[StormcloudVideoPlayer] IMA all_ads_completed event received");
901
960
  }
902
961
  });
903
962
  this.ima.on("ad_error", () => {
@@ -928,6 +987,26 @@ var StormcloudVideoPlayer = class {
928
987
  );
929
988
  }
930
989
  this.clearAdFailsafeTimer();
990
+ if (!this.inAdBreak) return;
991
+ const remaining = this.getRemainingAdMs();
992
+ if (remaining > 500 && this.adPodQueue.length > 0) {
993
+ const next = this.adPodQueue.shift();
994
+ this.currentAdIndex++;
995
+ if (this.config.debugAdTiming) {
996
+ console.log(
997
+ `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak})`
998
+ );
999
+ }
1000
+ this.playSingleAd(next).catch(() => {
1001
+ });
1002
+ } else {
1003
+ if (this.config.debugAdTiming) {
1004
+ console.log("[StormcloudVideoPlayer] Ad pod completed");
1005
+ }
1006
+ this.currentAdIndex = 0;
1007
+ this.totalAdsInBreak = 0;
1008
+ this.showAds = false;
1009
+ }
931
1010
  });
932
1011
  this.video.addEventListener("timeupdate", () => {
933
1012
  this.onTimeUpdate(this.video.currentTime);
@@ -1328,17 +1407,21 @@ var StormcloudVideoPlayer = class {
1328
1407
  return void 0;
1329
1408
  }
1330
1409
  initializeTracking() {
1331
- sendInitialTracking(this.config.licenseKey).catch((error) => {
1410
+ sendInitialTracking(this.config.licenseKey).then(() => {
1411
+ this.heartbeatInterval = window.setInterval(() => {
1412
+ this.sendHeartbeatIfNeeded();
1413
+ }, 5e3);
1414
+ }).catch((error) => {
1332
1415
  if (this.config.debugAdTiming) {
1333
1416
  console.warn(
1334
1417
  "[StormcloudVideoPlayer] Failed to send initial tracking:",
1335
1418
  error
1336
1419
  );
1337
1420
  }
1421
+ this.heartbeatInterval = window.setInterval(() => {
1422
+ this.sendHeartbeatIfNeeded();
1423
+ }, 5e3);
1338
1424
  });
1339
- this.heartbeatInterval = window.setInterval(() => {
1340
- this.sendHeartbeatIfNeeded();
1341
- }, 5e3);
1342
1425
  }
1343
1426
  sendHeartbeatIfNeeded() {
1344
1427
  const now = Date.now();
@@ -1573,9 +1656,21 @@ var StormcloudVideoPlayer = class {
1573
1656
  if (this.config.debugAdTiming) {
1574
1657
  console.log("[StormcloudVideoPlayer] Attempting to play ad:", vastTagUrl);
1575
1658
  }
1659
+ if (this.ima.isAdPlaying()) {
1660
+ if (this.config.debugAdTiming) {
1661
+ console.warn(
1662
+ "[StormcloudVideoPlayer] Ad already playing - skipping new ad request"
1663
+ );
1664
+ }
1665
+ return;
1666
+ }
1667
+ this.ima.updateOriginalMutedState(this.video.muted);
1576
1668
  this.startAdFailsafeTimer();
1577
1669
  try {
1578
1670
  await this.ima.requestAds(vastTagUrl);
1671
+ if (this.config.debugAdTiming) {
1672
+ console.log("[StormcloudVideoPlayer] Ad request successful, starting playback");
1673
+ }
1579
1674
  await this.ima.play();
1580
1675
  if (this.config.debugAdTiming) {
1581
1676
  console.log("[StormcloudVideoPlayer] Ad playback started successfully");
@@ -1603,6 +1698,13 @@ var StormcloudVideoPlayer = class {
1603
1698
  this.showAds = false;
1604
1699
  this.currentAdIndex = 0;
1605
1700
  this.totalAdsInBreak = 0;
1701
+ const originalMutedState = this.ima.getOriginalMutedState();
1702
+ this.video.muted = originalMutedState;
1703
+ if (this.config.debugAdTiming) {
1704
+ console.log(
1705
+ `[StormcloudVideoPlayer] Restored mute state to: ${originalMutedState}`
1706
+ );
1707
+ }
1606
1708
  if (this.video.paused) {
1607
1709
  this.video.play()?.catch(() => {
1608
1710
  if (this.config.debugAdTiming) {
@@ -2546,39 +2648,144 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
2546
2648
  left: "50%",
2547
2649
  transform: "translateX(-50%)",
2548
2650
  marginBottom: "4px",
2549
- background: "linear-gradient(135deg, rgba(0, 0, 0, 0.85) 0%, rgba(20, 20, 20, 0.9) 100%)",
2651
+ background: "linear-gradient(135deg, rgba(0, 0, 0, 0.88) 0%, rgba(20, 20, 20, 0.92) 100%)",
2550
2652
  backdropFilter: "blur(15px)",
2551
- padding: "16px 12px",
2552
- borderRadius: "12px",
2553
- border: "1px solid rgba(255, 255, 255, 0.1)",
2653
+ padding: "10px 14px",
2654
+ borderRadius: "14px",
2655
+ border: "1px solid rgba(255, 255, 255, 0.15)",
2554
2656
  display: "flex",
2555
2657
  flexDirection: "column",
2556
2658
  alignItems: "center",
2557
- height: "130px",
2558
- boxShadow: "0 12px 40px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1)",
2559
- zIndex: 10
2659
+ justifyContent: "center",
2660
+ height: "128px",
2661
+ boxShadow: "0 12px 40px rgba(0, 0, 0, 0.5), 0 4px 12px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.15)",
2662
+ zIndex: 10,
2663
+ transition: "transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out, border-color 0.2s ease-in-out"
2560
2664
  },
2561
- onMouseEnter: () => setShowVolumeSlider(true),
2562
- onMouseLeave: () => setShowVolumeSlider(false),
2563
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2564
- "input",
2665
+ onMouseEnter: (e) => {
2666
+ setShowVolumeSlider(true);
2667
+ e.currentTarget.style.transform = "translateX(-50%) translateY(-2px) scale(1.02)";
2668
+ e.currentTarget.style.boxShadow = "0 16px 48px rgba(0, 0, 0, 0.6), 0 6px 16px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 0 24px rgba(59, 130, 246, 0.3)";
2669
+ e.currentTarget.style.borderColor = "rgba(59, 130, 246, 0.4)";
2670
+ },
2671
+ onMouseLeave: (e) => {
2672
+ setShowVolumeSlider(false);
2673
+ e.currentTarget.style.transform = "translateX(-50%)";
2674
+ e.currentTarget.style.boxShadow = "0 12px 40px rgba(0, 0, 0, 0.5), 0 4px 12px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.15)";
2675
+ e.currentTarget.style.borderColor = "rgba(255, 255, 255, 0.15)";
2676
+ },
2677
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2678
+ "div",
2565
2679
  {
2566
- type: "range",
2567
- min: "0",
2568
- max: "1",
2569
- step: "0.01",
2570
- value: isMuted ? 0 : volume,
2571
- onChange: (e) => handleVolumeChange(parseFloat(e.target.value)),
2572
2680
  style: {
2573
- writingMode: "bt-lr",
2574
- WebkitAppearance: "slider-vertical",
2575
- width: "6px",
2576
- height: "90px",
2577
- background: "linear-gradient(180deg, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0.1) 100%)",
2578
- borderRadius: "3px",
2579
- outline: "none",
2580
- cursor: "pointer"
2581
- }
2681
+ position: "relative",
2682
+ width: "8px",
2683
+ height: "104px",
2684
+ cursor: "pointer",
2685
+ transition: "transform 0.2s ease-in-out"
2686
+ },
2687
+ onMouseEnter: (e) => {
2688
+ e.currentTarget.style.transform = "scaleX(1.2)";
2689
+ },
2690
+ onMouseLeave: (e) => {
2691
+ e.currentTarget.style.transform = "scaleX(1)";
2692
+ },
2693
+ onMouseDown: (e) => {
2694
+ e.preventDefault();
2695
+ const sliderElement = e.currentTarget;
2696
+ const handleMouseMove = (moveEvent) => {
2697
+ if (!sliderElement) return;
2698
+ const rect2 = sliderElement.getBoundingClientRect();
2699
+ const y2 = moveEvent.clientY - rect2.top;
2700
+ const percentage2 = 1 - Math.max(0, Math.min(1, y2 / rect2.height));
2701
+ handleVolumeChange(percentage2);
2702
+ };
2703
+ const handleMouseUp = () => {
2704
+ document.removeEventListener("mousemove", handleMouseMove);
2705
+ document.removeEventListener("mouseup", handleMouseUp);
2706
+ };
2707
+ document.addEventListener("mousemove", handleMouseMove);
2708
+ document.addEventListener("mouseup", handleMouseUp);
2709
+ const rect = sliderElement.getBoundingClientRect();
2710
+ const y = e.clientY - rect.top;
2711
+ const percentage = 1 - Math.max(0, Math.min(1, y / rect.height));
2712
+ handleVolumeChange(percentage);
2713
+ },
2714
+ onClick: (e) => {
2715
+ e.stopPropagation();
2716
+ const rect = e.currentTarget.getBoundingClientRect();
2717
+ const y = e.clientY - rect.top;
2718
+ const percentage = 1 - Math.max(0, Math.min(1, y / rect.height));
2719
+ handleVolumeChange(percentage);
2720
+ },
2721
+ children: [
2722
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2723
+ "div",
2724
+ {
2725
+ style: {
2726
+ position: "absolute",
2727
+ bottom: "0",
2728
+ left: "0",
2729
+ width: "100%",
2730
+ height: "100%",
2731
+ background: "linear-gradient(180deg, rgba(255, 255, 255, 0.4) 0%, rgba(255, 255, 255, 0.15) 100%)",
2732
+ borderRadius: "4px",
2733
+ boxShadow: "inset 0 1px 3px rgba(0, 0, 0, 0.2)"
2734
+ }
2735
+ }
2736
+ ),
2737
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2738
+ "div",
2739
+ {
2740
+ style: {
2741
+ position: "absolute",
2742
+ bottom: "0",
2743
+ left: "0",
2744
+ width: "100%",
2745
+ height: `${(isMuted ? 0 : volume) * 100}%`,
2746
+ background: "linear-gradient(180deg, rgba(96, 165, 250, 1) 0%, rgba(59, 130, 246, 0.95) 50%, rgba(37, 99, 235, 0.9) 100%)",
2747
+ borderRadius: "4px",
2748
+ transition: "height 0.15s ease-out, box-shadow 0.2s ease-in-out",
2749
+ boxShadow: "0 0 8px rgba(59, 130, 246, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.3)"
2750
+ }
2751
+ }
2752
+ ),
2753
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2754
+ "div",
2755
+ {
2756
+ style: {
2757
+ position: "absolute",
2758
+ bottom: `calc(${(isMuted ? 0 : volume) * 100}% - 7px)`,
2759
+ left: "50%",
2760
+ transform: "translateX(-50%)",
2761
+ width: "14px",
2762
+ height: "14px",
2763
+ background: "linear-gradient(135deg, #ffffff 0%, #f0f9ff 100%)",
2764
+ borderRadius: "50%",
2765
+ boxShadow: "0 2px 6px rgba(0, 0, 0, 0.3), 0 0 0 2px rgba(59, 130, 246, 0.3), 0 0 12px rgba(59, 130, 246, 0.4)",
2766
+ transition: "bottom 0.15s ease-out, transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out, width 0.2s ease-in-out, height 0.2s ease-in-out",
2767
+ cursor: "grab"
2768
+ },
2769
+ onMouseEnter: (e) => {
2770
+ e.currentTarget.style.transform = "translateX(-50%) scale(1.3)";
2771
+ e.currentTarget.style.boxShadow = "0 3px 10px rgba(0, 0, 0, 0.4), 0 0 0 3px rgba(59, 130, 246, 0.5), 0 0 20px rgba(59, 130, 246, 0.6)";
2772
+ e.currentTarget.style.cursor = "grab";
2773
+ },
2774
+ onMouseLeave: (e) => {
2775
+ e.currentTarget.style.transform = "translateX(-50%) scale(1)";
2776
+ e.currentTarget.style.boxShadow = "0 2px 6px rgba(0, 0, 0, 0.3), 0 0 0 2px rgba(59, 130, 246, 0.3), 0 0 12px rgba(59, 130, 246, 0.4)";
2777
+ },
2778
+ onMouseDown: (e) => {
2779
+ e.currentTarget.style.transform = "translateX(-50%) scale(1.4)";
2780
+ e.currentTarget.style.cursor = "grabbing";
2781
+ },
2782
+ onMouseUp: (e) => {
2783
+ e.currentTarget.style.transform = "translateX(-50%) scale(1.3)";
2784
+ e.currentTarget.style.cursor = "grab";
2785
+ }
2786
+ }
2787
+ )
2788
+ ]
2582
2789
  }
2583
2790
  )
2584
2791
  }
@@ -2895,40 +3102,146 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
2895
3102
  left: "50%",
2896
3103
  transform: "translateX(-50%)",
2897
3104
  marginBottom: "4px",
2898
- background: "linear-gradient(135deg, rgba(0, 0, 0, 0.95) 0%, rgba(20, 20, 20, 0.9) 100%)",
3105
+ background: "linear-gradient(135deg, rgba(0, 0, 0, 0.96) 0%, rgba(20, 20, 20, 0.92) 100%)",
2899
3106
  backdropFilter: "blur(20px)",
2900
- padding: "16px 12px",
2901
- borderRadius: "12px",
2902
- border: "2px solid rgba(255, 255, 255, 0.6)",
3107
+ padding: "10px 14px",
3108
+ borderRadius: "14px",
3109
+ border: "2px solid rgba(255, 255, 255, 0.7)",
2903
3110
  display: "flex",
2904
3111
  flexDirection: "column",
2905
3112
  alignItems: "center",
2906
- height: "130px",
2907
- boxShadow: "0 12px 40px rgba(0, 0, 0, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.3)",
2908
- zIndex: 10
3113
+ justifyContent: "center",
3114
+ height: "128px",
3115
+ boxShadow: "0 12px 40px rgba(0, 0, 0, 0.85), 0 4px 12px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.35)",
3116
+ zIndex: 10,
3117
+ transition: "transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out, border-color 0.2s ease-in-out"
2909
3118
  },
2910
- onMouseEnter: () => setShowVolumeSlider(true),
2911
- onMouseLeave: () => setShowVolumeSlider(false),
2912
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2913
- "input",
3119
+ onMouseEnter: (e) => {
3120
+ setShowVolumeSlider(true);
3121
+ e.currentTarget.style.transform = "translateX(-50%) translateY(-2px) scale(1.02)";
3122
+ e.currentTarget.style.boxShadow = "0 16px 48px rgba(0, 0, 0, 0.9), 0 6px 16px rgba(0, 0, 0, 0.7), inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 24px rgba(96, 165, 250, 0.4)";
3123
+ e.currentTarget.style.borderColor = "rgba(96, 165, 250, 0.8)";
3124
+ },
3125
+ onMouseLeave: (e) => {
3126
+ setShowVolumeSlider(false);
3127
+ e.currentTarget.style.transform = "translateX(-50%)";
3128
+ e.currentTarget.style.boxShadow = "0 12px 40px rgba(0, 0, 0, 0.85), 0 4px 12px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.35)";
3129
+ e.currentTarget.style.borderColor = "rgba(255, 255, 255, 0.7)";
3130
+ },
3131
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
3132
+ "div",
2914
3133
  {
2915
- type: "range",
2916
- min: "0",
2917
- max: "1",
2918
- step: "0.01",
2919
- value: isMuted ? 0 : volume,
2920
- onChange: (e) => handleVolumeChange(parseFloat(e.target.value)),
2921
3134
  style: {
2922
- writingMode: "bt-lr",
2923
- WebkitAppearance: "slider-vertical",
2924
- width: "6px",
2925
- height: "90px",
2926
- background: "linear-gradient(180deg, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0.4) 100%)",
2927
- borderRadius: "3px",
2928
- outline: "none",
3135
+ position: "relative",
3136
+ width: "8px",
3137
+ height: "104px",
2929
3138
  cursor: "pointer",
2930
- border: "1px solid rgba(255, 255, 255, 0.3)"
2931
- }
3139
+ transition: "transform 0.2s ease-in-out"
3140
+ },
3141
+ onMouseEnter: (e) => {
3142
+ e.currentTarget.style.transform = "scaleX(1.2)";
3143
+ },
3144
+ onMouseLeave: (e) => {
3145
+ e.currentTarget.style.transform = "scaleX(1)";
3146
+ },
3147
+ onMouseDown: (e) => {
3148
+ e.preventDefault();
3149
+ const sliderElement = e.currentTarget;
3150
+ const handleMouseMove = (moveEvent) => {
3151
+ if (!sliderElement) return;
3152
+ const rect2 = sliderElement.getBoundingClientRect();
3153
+ const y2 = moveEvent.clientY - rect2.top;
3154
+ const percentage2 = 1 - Math.max(0, Math.min(1, y2 / rect2.height));
3155
+ handleVolumeChange(percentage2);
3156
+ };
3157
+ const handleMouseUp = () => {
3158
+ document.removeEventListener("mousemove", handleMouseMove);
3159
+ document.removeEventListener("mouseup", handleMouseUp);
3160
+ };
3161
+ document.addEventListener("mousemove", handleMouseMove);
3162
+ document.addEventListener("mouseup", handleMouseUp);
3163
+ const rect = sliderElement.getBoundingClientRect();
3164
+ const y = e.clientY - rect.top;
3165
+ const percentage = 1 - Math.max(0, Math.min(1, y / rect.height));
3166
+ handleVolumeChange(percentage);
3167
+ },
3168
+ onClick: (e) => {
3169
+ e.stopPropagation();
3170
+ const rect = e.currentTarget.getBoundingClientRect();
3171
+ const y = e.clientY - rect.top;
3172
+ const percentage = 1 - Math.max(0, Math.min(1, y / rect.height));
3173
+ handleVolumeChange(percentage);
3174
+ },
3175
+ children: [
3176
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3177
+ "div",
3178
+ {
3179
+ style: {
3180
+ position: "absolute",
3181
+ bottom: "0",
3182
+ left: "0",
3183
+ width: "100%",
3184
+ height: "100%",
3185
+ background: "linear-gradient(180deg, rgba(255, 255, 255, 0.85) 0%, rgba(255, 255, 255, 0.5) 100%)",
3186
+ borderRadius: "4px",
3187
+ border: "1px solid rgba(255, 255, 255, 0.4)",
3188
+ boxShadow: "inset 0 1px 3px rgba(0, 0, 0, 0.3)"
3189
+ }
3190
+ }
3191
+ ),
3192
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3193
+ "div",
3194
+ {
3195
+ style: {
3196
+ position: "absolute",
3197
+ bottom: "0",
3198
+ left: "0",
3199
+ width: "100%",
3200
+ height: `${(isMuted ? 0 : volume) * 100}%`,
3201
+ background: "linear-gradient(180deg, rgba(125, 211, 252, 1) 0%, rgba(96, 165, 250, 0.98) 50%, rgba(59, 130, 246, 0.95) 100%)",
3202
+ borderRadius: "4px",
3203
+ transition: "height 0.15s ease-out, box-shadow 0.2s ease-in-out",
3204
+ boxShadow: "0 0 12px rgba(96, 165, 250, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.4)"
3205
+ }
3206
+ }
3207
+ ),
3208
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3209
+ "div",
3210
+ {
3211
+ style: {
3212
+ position: "absolute",
3213
+ bottom: `calc(${(isMuted ? 0 : volume) * 100}% - 8px)`,
3214
+ left: "50%",
3215
+ transform: "translateX(-50%)",
3216
+ width: "16px",
3217
+ height: "16px",
3218
+ background: "linear-gradient(135deg, #ffffff 0%, #f0f9ff 100%)",
3219
+ borderRadius: "50%",
3220
+ border: "2px solid rgba(96, 165, 250, 0.9)",
3221
+ boxShadow: "0 3px 8px rgba(0, 0, 0, 0.5), 0 0 0 2px rgba(96, 165, 250, 0.4), 0 0 16px rgba(96, 165, 250, 0.5)",
3222
+ transition: "bottom 0.15s ease-out, transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out, width 0.2s ease-in-out, height 0.2s ease-in-out",
3223
+ cursor: "grab"
3224
+ },
3225
+ onMouseEnter: (e) => {
3226
+ e.currentTarget.style.transform = "translateX(-50%) scale(1.35)";
3227
+ e.currentTarget.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.6), 0 0 0 3px rgba(96, 165, 250, 0.6), 0 0 24px rgba(96, 165, 250, 0.7)";
3228
+ e.currentTarget.style.cursor = "grab";
3229
+ },
3230
+ onMouseLeave: (e) => {
3231
+ e.currentTarget.style.transform = "translateX(-50%) scale(1)";
3232
+ e.currentTarget.style.boxShadow = "0 3px 8px rgba(0, 0, 0, 0.5), 0 0 0 2px rgba(96, 165, 250, 0.4), 0 0 16px rgba(96, 165, 250, 0.5)";
3233
+ },
3234
+ onMouseDown: (e) => {
3235
+ e.currentTarget.style.transform = "translateX(-50%) scale(1.45)";
3236
+ e.currentTarget.style.cursor = "grabbing";
3237
+ },
3238
+ onMouseUp: (e) => {
3239
+ e.currentTarget.style.transform = "translateX(-50%) scale(1.35)";
3240
+ e.currentTarget.style.cursor = "grab";
3241
+ }
3242
+ }
3243
+ )
3244
+ ]
2932
3245
  }
2933
3246
  )
2934
3247
  }