@shortkitsdk/web 0.3.0 → 0.3.2

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.
package/dist/shortkit.js CHANGED
@@ -33546,16 +33546,35 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
33546
33546
  player._skCurrentUrl = streamingUrl;
33547
33547
  }
33548
33548
  /**
33549
- * Promote a preload player to active: raise buffer limits and uncap ABR.
33550
- * Safe to call even if the HLS instance was already created with active config.
33549
+ * Promote a preload player to active: raise buffer limits, uncap ABR, and
33550
+ * force an immediate quality upgrade.
33551
+ *
33552
+ * Preload streams start at level 0 (lowest rendition) to conserve bandwidth.
33553
+ * When attachStream() is called again with isActive=true for the same URL it
33554
+ * early-returns, so the HLS instance keeps its startLevel:0 config. The
33555
+ * already-buffered low-quality segments play through before any higher-quality
33556
+ * segments arrive — causing a visible low-res start even on fast connections.
33557
+ *
33558
+ * Fix: lock HLS to the highest level, then seek-in-place to flush the
33559
+ * low-quality buffer so the player immediately re-fetches at high quality.
33560
+ * Re-enable ABR after a few seconds so slow connections self-correct.
33551
33561
  */
33552
33562
  promoteToActive(itemId) {
33553
33563
  const hls = this.hlsInstances.get(itemId);
33554
- if (hls) {
33555
- hls.config.maxBufferLength = PlayerPool.ACTIVE_HLS_CONFIG.maxBufferLength;
33556
- hls.config.maxMaxBufferLength = PlayerPool.ACTIVE_HLS_CONFIG.maxMaxBufferLength;
33557
- hls.autoLevelCapping = -1;
33558
- hls.nextAutoLevel = -1;
33564
+ if (!hls) return;
33565
+ hls.config.maxBufferLength = PlayerPool.ACTIVE_HLS_CONFIG.maxBufferLength;
33566
+ hls.config.maxMaxBufferLength = PlayerPool.ACTIVE_HLS_CONFIG.maxMaxBufferLength;
33567
+ hls.autoLevelCapping = -1;
33568
+ const top = hls.levels ? hls.levels.length - 1 : -1;
33569
+ if (top > 0 && hls.currentLevel < top) {
33570
+ hls.currentLevel = top;
33571
+ const player = this.assignments.get(itemId);
33572
+ if (player) {
33573
+ player.currentTime = player.currentTime;
33574
+ }
33575
+ setTimeout(() => {
33576
+ if (!hls.destroyed) hls.currentLevel = -1;
33577
+ }, 4e3);
33559
33578
  }
33560
33579
  }
33561
33580
  /** Pause, destroy HLS, and remove player from DOM and pool tracking. */
@@ -33851,7 +33870,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
33851
33870
  }
33852
33871
  setMuted(muted) {
33853
33872
  this.isMuted = muted;
33854
- for (const [, player] of this.pool._players) {
33873
+ for (const [, player] of this.pool.assignments) {
33855
33874
  player.muted = muted;
33856
33875
  }
33857
33876
  this._pushPlayerState({ isMuted: muted });
@@ -33864,7 +33883,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
33864
33883
  }
33865
33884
  setPlaybackRate(rate) {
33866
33885
  this.playbackRate = rate;
33867
- for (const [, player] of this.pool._players) {
33886
+ for (const [, player] of this.pool.assignments) {
33868
33887
  player.playbackRate = rate;
33869
33888
  }
33870
33889
  this._pushPlayerState({ playbackRate: rate });
@@ -34341,11 +34360,10 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
34341
34360
  const player = this.pool.getPlayer(id);
34342
34361
  if (player) {
34343
34362
  player.pause();
34344
- player.style.opacity = "0";
34345
34363
  player._skRevealedFor = null;
34346
34364
  }
34347
34365
  this._stopTimeLoop(id);
34348
- this._clearOverlay(id);
34366
+ this._detachOverlay(id);
34349
34367
  this._sk._tracker.deactivateContent();
34350
34368
  }
34351
34369
  // --- Time loop ---
@@ -34527,14 +34545,20 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
34527
34545
  }
34528
34546
  }
34529
34547
  /** Clear the overlay container's DOM and unsubscribe tracked listeners. */
34530
- _clearOverlay(itemId) {
34548
+ /** Unsubscribe overlay event listeners without clearing DOM. */
34549
+ _detachOverlay(itemId) {
34531
34550
  const entry = this._overlayContainers.get(itemId);
34532
34551
  if (!entry) return;
34533
34552
  if (entry.unsub) {
34534
34553
  entry.unsub();
34535
34554
  entry.unsub = null;
34536
34555
  }
34537
- entry.el.innerHTML = "";
34556
+ }
34557
+ /** Unsubscribe and clear overlay DOM (used on destroy). */
34558
+ _clearOverlay(itemId) {
34559
+ this._detachOverlay(itemId);
34560
+ const entry = this._overlayContainers.get(itemId);
34561
+ if (entry) entry.el.innerHTML = "";
34538
34562
  }
34539
34563
  }
34540
34564
  class EmbeddedFeedManager {
@@ -34583,6 +34607,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
34583
34607
  this._setupScrollEnd();
34584
34608
  this._setupVisibilityHandler();
34585
34609
  this._setupCellHeightObserver();
34610
+ this._setupDebugPanel();
34586
34611
  if (startIndex > 0 && startIndex < items.length) {
34587
34612
  const targetEl = this.itemEls.get(items[startIndex].id);
34588
34613
  if (targetEl) targetEl.scrollIntoView({ behavior: "instant", block: "start" });
@@ -34605,6 +34630,11 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
34605
34630
  this._resizeObserver = null;
34606
34631
  }
34607
34632
  if (this._visHandler) document.removeEventListener("visibilitychange", this._visHandler);
34633
+ if (this._debugKeyHandler) document.removeEventListener("keydown", this._debugKeyHandler);
34634
+ if (this._debugPanel) {
34635
+ this._debugPanel.remove();
34636
+ this._debugPanel = null;
34637
+ }
34608
34638
  this.container.innerHTML = "";
34609
34639
  this.itemEls.clear();
34610
34640
  this.items = [];
@@ -34648,7 +34678,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
34648
34678
  }
34649
34679
  setMuted(muted) {
34650
34680
  this.isMuted = muted;
34651
- for (const [, player] of this.pool._players) {
34681
+ for (const [, player] of this.pool.assignments) {
34652
34682
  player.muted = muted;
34653
34683
  }
34654
34684
  this._pushPlayerState({ isMuted: muted });
@@ -34661,7 +34691,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
34661
34691
  }
34662
34692
  setPlaybackRate(rate) {
34663
34693
  this.playbackRate = rate;
34664
- for (const [, player] of this.pool._players) {
34694
+ for (const [, player] of this.pool.assignments) {
34665
34695
  player.playbackRate = rate;
34666
34696
  }
34667
34697
  this._pushPlayerState({ playbackRate: rate });
@@ -34933,11 +34963,52 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
34933
34963
  const p = this.pool.getPlayer(id);
34934
34964
  if (p) {
34935
34965
  p.pause();
34936
- p.style.opacity = "0";
34937
34966
  p._skRevealedFor = null;
34938
34967
  }
34939
34968
  this._stopTimeLoop(id);
34940
- this._clearOverlay(id);
34969
+ this._detachOverlay(id);
34970
+ }
34971
+ // --- Debug panel (toggle with 'D' key) ---
34972
+ _setupDebugPanel() {
34973
+ const panel = document.createElement("div");
34974
+ panel.id = "sk-debug-panel";
34975
+ panel.style.cssText = "position:fixed;top:12px;right:12px;z-index:99999;background:rgba(0,0,0,.85);color:#0f0;font:12px/1.5 monospace;padding:10px 14px;border-radius:8px;pointer-events:none;display:none;min-width:220px;";
34976
+ document.body.appendChild(panel);
34977
+ this._debugPanel = panel;
34978
+ this._debugVisible = false;
34979
+ this._debugKeyHandler = (e) => {
34980
+ if (e.key === "d" || e.key === "D") {
34981
+ this._debugVisible = !this._debugVisible;
34982
+ panel.style.display = this._debugVisible ? "block" : "none";
34983
+ }
34984
+ };
34985
+ document.addEventListener("keydown", this._debugKeyHandler);
34986
+ }
34987
+ _updateDebugPanel() {
34988
+ if (!this._debugVisible || !this._debugPanel) return;
34989
+ const hls = this.pool.hlsInstances.get(this.activeItemId);
34990
+ if (!hls || !hls.levels || !hls.levels.length) {
34991
+ this._debugPanel.innerHTML = "HLS: no levels loaded";
34992
+ return;
34993
+ }
34994
+ const level = hls.levels[hls.currentLevel] || {};
34995
+ const bw = hls.bandwidthEstimate ? (hls.bandwidthEstimate / 1e6).toFixed(2) : "?";
34996
+ const lines = [
34997
+ `<b style="color:#fff">HLS Debug</b>`,
34998
+ `Level: ${hls.currentLevel} / ${hls.levels.length - 1}`,
34999
+ `Resolution: ${level.width || "?"}x${level.height || "?"}`,
35000
+ `Bitrate: ${level.bitrate ? (level.bitrate / 1e3).toFixed(0) + " kbps" : "?"}`,
35001
+ `BW estimate: ${bw} Mbps`,
35002
+ `Auto cap: ${hls.autoLevelCapping}`,
35003
+ `Next level: ${hls.nextLevel}`,
35004
+ `<span style="color:#888">Levels:</span>`
35005
+ ];
35006
+ for (let i = 0; i < hls.levels.length; i++) {
35007
+ const l = hls.levels[i];
35008
+ const marker = i === hls.currentLevel ? ' <b style="color:#0ff">◀</b>' : "";
35009
+ lines.push(` ${i}: ${l.width}x${l.height} @ ${(l.bitrate / 1e3).toFixed(0)}k${marker}`);
35010
+ }
35011
+ this._debugPanel.innerHTML = lines.join("<br>");
34941
35012
  }
34942
35013
  // --- Time loop (state emission only, no DOM updates) ---
34943
35014
  _startTimeLoop(id, el, player) {
@@ -34945,6 +35016,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
34945
35016
  const tick = () => {
34946
35017
  if (this._destroyed) return;
34947
35018
  if (!player || player.paused) {
35019
+ this._updateDebugPanel();
34948
35020
  this.rafIds.set(id, requestAnimationFrame(tick));
34949
35021
  return;
34950
35022
  }
@@ -34970,6 +35042,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
34970
35042
  player.play().catch(() => {
34971
35043
  });
34972
35044
  }
35045
+ this._updateDebugPanel();
34973
35046
  this.rafIds.set(id, requestAnimationFrame(tick));
34974
35047
  };
34975
35048
  this.rafIds.set(id, requestAnimationFrame(tick));
@@ -35136,14 +35209,20 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
35136
35209
  } catch (e) {
35137
35210
  }
35138
35211
  }
35139
- _clearOverlay(itemId) {
35212
+ /** Unsubscribe overlay event listeners without clearing DOM. */
35213
+ _detachOverlay(itemId) {
35140
35214
  const entry = this._overlayContainers.get(itemId);
35141
35215
  if (!entry) return;
35142
35216
  if (entry.unsub) {
35143
35217
  entry.unsub();
35144
35218
  entry.unsub = null;
35145
35219
  }
35146
- entry.el.innerHTML = "";
35220
+ }
35221
+ /** Unsubscribe and clear overlay DOM (used on destroy). */
35222
+ _clearOverlay(itemId) {
35223
+ this._detachOverlay(itemId);
35224
+ const entry = this._overlayContainers.get(itemId);
35225
+ if (entry) entry.el.innerHTML = "";
35147
35226
  }
35148
35227
  }
35149
35228
  const MuteOnSvg = '<svg viewBox="0 0 24 24"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/></svg>';
@@ -35493,6 +35572,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
35493
35572
  this._isOpen = true;
35494
35573
  this._onClose = onClose;
35495
35574
  this._openItemId = item.id;
35575
+ this._openThumbnailUrl = item.thumbnailUrl || null;
35496
35576
  const sourceRect = sourceEl.getBoundingClientRect();
35497
35577
  const sourceRadius = parseFloat(getComputedStyle(sourceEl).borderRadius) || 12;
35498
35578
  this.overlayEl.classList.add("skp-active");
@@ -35537,6 +35617,21 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
35537
35617
  feedEl.style.transform = "none";
35538
35618
  feedEl.style.borderRadius = `${targetRadius}px`;
35539
35619
  });
35620
+ this.feedManager.startObserver();
35621
+ this.feedManager._activateItem(item.id);
35622
+ this.overlayEl.classList.add("skp-feed-ready");
35623
+ const itemOverlay = feedItemEl?.querySelector('[data-ref="overlay"]');
35624
+ const chromeEls = [...this.overlayEl.querySelectorAll(".skp-feed-close, .sk-sidebar")];
35625
+ if (itemOverlay) chromeEls.push(itemOverlay);
35626
+ chromeEls.forEach((el) => {
35627
+ el.style.opacity = "0";
35628
+ el.style.transition = "none";
35629
+ });
35630
+ this.overlayEl.getBoundingClientRect();
35631
+ chromeEls.forEach((el) => {
35632
+ el.style.transition = "opacity .35s cubic-bezier(.32,.72,0,1)";
35633
+ el.style.opacity = "1";
35634
+ });
35540
35635
  await this._waitForTransitionEnd(feedEl, 450);
35541
35636
  feedEl.classList.remove("skp-flip-animating");
35542
35637
  feedEl.style.transformOrigin = "";
@@ -35544,9 +35639,10 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
35544
35639
  feedEl.style.scrollSnapType = "";
35545
35640
  feedEl.style.transform = "";
35546
35641
  feedEl.style.borderRadius = "";
35547
- this.feedManager.startObserver();
35548
- this.feedManager._activateItem(item.id);
35549
- this.overlayEl.classList.add("skp-feed-ready");
35642
+ chromeEls.forEach((el) => {
35643
+ el.style.opacity = "";
35644
+ el.style.transition = "";
35645
+ });
35550
35646
  this._escHandler = (e) => {
35551
35647
  if (e.key === "Escape") this.close();
35552
35648
  };
@@ -35559,33 +35655,44 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
35559
35655
  const feedWrapper = feedEl.parentNode;
35560
35656
  const feedRect = feedEl.getBoundingClientRect();
35561
35657
  const activeItemId = this.feedManager?.activeItemId;
35562
- let transferBack = false, transferVideo = null, transferHls = null;
35563
- if (activeItemId === this._openItemId) {
35564
- const ejected = this.feedManager?.pool.ejectPlayer(activeItemId);
35565
- if (ejected) {
35566
- transferBack = true;
35567
- transferVideo = ejected.video;
35568
- transferHls = ejected.hls;
35569
- }
35570
- }
35658
+ const canTransfer = activeItemId === this._openItemId;
35571
35659
  let targetRect = null;
35572
35660
  if (this._onClose) targetRect = this._onClose("getSourceRect");
35573
35661
  if (feedRect && targetRect) {
35574
- if (this.feedManager?.activeItemId && !transferBack) {
35662
+ if (this.feedManager?.activeItemId && !canTransfer) {
35575
35663
  this.feedManager._deactivateItem(this.feedManager.activeItemId);
35576
35664
  }
35665
+ const targetItemId = canTransfer ? this._openItemId : activeItemId;
35666
+ if (targetItemId && this.feedManager) {
35667
+ const targetEl = this.feedManager.itemEls.get(targetItemId);
35668
+ if (targetEl) targetEl.scrollIntoView({ behavior: "instant", block: "start" });
35669
+ }
35670
+ const savedScrollTop = feedEl.scrollTop;
35577
35671
  feedEl.style.position = "fixed";
35578
35672
  feedEl.style.left = `${feedRect.left}px`;
35579
35673
  feedEl.style.top = `${feedRect.top}px`;
35580
35674
  feedEl.style.width = `${feedRect.width}px`;
35581
35675
  feedEl.style.height = `${feedRect.height}px`;
35582
- feedEl.style.zIndex = "9999";
35676
+ feedEl.style.zIndex = String(getComputedStyle(this.overlayEl).zIndex || 9999);
35583
35677
  feedEl.style.margin = "0";
35584
35678
  feedEl.style.flex = "none";
35585
35679
  feedEl.style.aspectRatio = "unset";
35586
35680
  feedEl.style.maxHeight = "none";
35587
35681
  document.body.appendChild(feedEl);
35682
+ feedEl.scrollTop = savedScrollTop;
35588
35683
  this.overlayEl.classList.remove("skp-active", "skp-feed-ready");
35684
+ let thumbOverlay = null;
35685
+ if (this._openThumbnailUrl) {
35686
+ const crossfadeItemId = canTransfer ? this._openItemId : activeItemId;
35687
+ const crossfadeEl = crossfadeItemId && this.feedManager?.itemEls.get(crossfadeItemId);
35688
+ const videoContainer = crossfadeEl?.querySelector('[data-ref="videoContainer"]');
35689
+ if (videoContainer) {
35690
+ thumbOverlay = document.createElement("img");
35691
+ thumbOverlay.src = this._openThumbnailUrl;
35692
+ thumbOverlay.style.cssText = "position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .4s cubic-bezier(.32,.72,0,1);pointer-events:none;z-index:2;";
35693
+ videoContainer.appendChild(thumbOverlay);
35694
+ }
35695
+ }
35589
35696
  const scaleX = targetRect.width / feedRect.width;
35590
35697
  const scaleY = targetRect.height / feedRect.height;
35591
35698
  const translateX = targetRect.left - feedRect.left;
@@ -35595,29 +35702,58 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
35595
35702
  feedEl.style.transformOrigin = "0 0";
35596
35703
  feedEl.style.overflow = "hidden";
35597
35704
  feedEl.style.scrollSnapType = "none";
35705
+ feedEl.style.pointerEvents = "none";
35598
35706
  feedEl.getBoundingClientRect();
35599
35707
  feedEl.classList.add("skp-flip-animating");
35600
35708
  requestAnimationFrame(() => {
35601
35709
  feedEl.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`;
35602
35710
  feedEl.style.borderRadius = `${endRadius}px`;
35711
+ if (thumbOverlay) thumbOverlay.style.opacity = "1";
35603
35712
  });
35604
35713
  await this._waitForTransitionEnd(feedEl, 450);
35714
+ if (thumbOverlay) thumbOverlay.remove();
35715
+ let transferVideo = null;
35716
+ let transferHls = null;
35717
+ if (canTransfer) {
35718
+ const ejected = this.feedManager?.pool.ejectPlayer(this._openItemId);
35719
+ if (ejected) {
35720
+ transferVideo = ejected.video;
35721
+ transferHls = ejected.hls;
35722
+ }
35723
+ }
35605
35724
  feedEl.style.visibility = "hidden";
35606
35725
  feedEl.classList.remove("skp-flip-animating");
35607
- ["position", "left", "top", "width", "height", "zIndex", "margin", "flex", "aspectRatio", "maxHeight", "transformOrigin", "overflow", "scrollSnapType", "transform", "borderRadius"].forEach((p) => feedEl.style[p] = "");
35726
+ ["position", "left", "top", "width", "height", "zIndex", "margin", "flex", "aspectRatio", "maxHeight", "transformOrigin", "overflow", "scrollSnapType", "pointerEvents", "transform", "borderRadius"].forEach((p) => feedEl.style[p] = "");
35608
35727
  const sidebarEl = feedWrapper.querySelector(".sk-sidebar");
35609
35728
  feedWrapper.insertBefore(feedEl, sidebarEl);
35610
35729
  feedEl.style.visibility = "";
35730
+ if (this._onClose) {
35731
+ this._onClose("closed", {
35732
+ transferVideo,
35733
+ transferHls,
35734
+ transferItemId: transferVideo ? this._openItemId : null
35735
+ });
35736
+ this._onClose = null;
35737
+ }
35611
35738
  } else {
35612
35739
  this.overlayEl.classList.remove("skp-active", "skp-feed-ready");
35613
- }
35614
- if (this._onClose) {
35615
- this._onClose("closed", {
35616
- transferVideo: transferBack ? transferVideo : null,
35617
- transferHls: transferBack ? transferHls : null,
35618
- transferItemId: transferBack ? this._openItemId : null
35619
- });
35620
- this._onClose = null;
35740
+ if (this._onClose) {
35741
+ let transferVideo = null;
35742
+ let transferHls = null;
35743
+ if (canTransfer) {
35744
+ const ejected = this.feedManager?.pool.ejectPlayer(this._openItemId);
35745
+ if (ejected) {
35746
+ transferVideo = ejected.video;
35747
+ transferHls = ejected.hls;
35748
+ }
35749
+ }
35750
+ this._onClose("closed", {
35751
+ transferVideo,
35752
+ transferHls,
35753
+ transferItemId: transferVideo ? this._openItemId : null
35754
+ });
35755
+ this._onClose = null;
35756
+ }
35621
35757
  }
35622
35758
  if (this.feedManager) {
35623
35759
  this.feedManager.destroy();
@@ -35669,6 +35805,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
35669
35805
  this._onClose = onClose;
35670
35806
  this._openSlotIndex = startIndex;
35671
35807
  this._openItemId = item.id;
35808
+ this._openThumbnailUrl = item.thumbnailUrl || null;
35672
35809
  const slotRect = slotEl.getBoundingClientRect();
35673
35810
  const slotRadius = parseFloat(getComputedStyle(slotEl).borderRadius) || 12;
35674
35811
  if (_isPreview) {
@@ -35715,6 +35852,21 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
35715
35852
  feedEl.style.transform = "none";
35716
35853
  feedEl.style.borderRadius = `${targetRadius}px`;
35717
35854
  });
35855
+ this.feedManager.startObserver();
35856
+ this.feedManager._activateItem(item.id);
35857
+ this.overlayEl.classList.add("skw-feed-ready");
35858
+ const itemOverlay = feedItemEl?.querySelector('[data-ref="overlay"]');
35859
+ const chromeEls = [...this.overlayEl.querySelectorAll(".skw-feed-close, .sk-sidebar")];
35860
+ if (itemOverlay) chromeEls.push(itemOverlay);
35861
+ chromeEls.forEach((el) => {
35862
+ el.style.opacity = "0";
35863
+ el.style.transition = "none";
35864
+ });
35865
+ this.overlayEl.getBoundingClientRect();
35866
+ chromeEls.forEach((el) => {
35867
+ el.style.transition = "opacity .35s cubic-bezier(.32,.72,0,1)";
35868
+ el.style.opacity = "1";
35869
+ });
35718
35870
  await this._waitForTransitionEnd(feedEl, 450);
35719
35871
  feedEl.classList.remove("skw-flip-animating");
35720
35872
  feedEl.style.transformOrigin = "";
@@ -35722,9 +35874,10 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
35722
35874
  feedEl.style.scrollSnapType = "";
35723
35875
  feedEl.style.transform = "";
35724
35876
  feedEl.style.borderRadius = "";
35725
- this.feedManager.startObserver();
35726
- this.feedManager._activateItem(item.id);
35727
- this.overlayEl.classList.add("skw-feed-ready");
35877
+ chromeEls.forEach((el) => {
35878
+ el.style.opacity = "";
35879
+ el.style.transition = "";
35880
+ });
35728
35881
  this._escHandler = (e) => {
35729
35882
  if (e.key === "Escape") this.close();
35730
35883
  };
@@ -35751,6 +35904,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
35751
35904
  if (this.feedManager?.activeItemId && !canTransfer) {
35752
35905
  this.feedManager._deactivateItem(this.feedManager.activeItemId);
35753
35906
  }
35907
+ const targetItemId = canTransfer ? this._openItemId : activeItemId;
35908
+ if (targetItemId && this.feedManager) {
35909
+ const targetEl = this.feedManager.itemEls.get(targetItemId);
35910
+ if (targetEl) targetEl.scrollIntoView({ behavior: "instant", block: "start" });
35911
+ }
35912
+ const savedScrollTop = feedEl.scrollTop;
35754
35913
  feedEl.style.position = "fixed";
35755
35914
  feedEl.style.left = `${feedRect.left}px`;
35756
35915
  feedEl.style.top = `${feedRect.top}px`;
@@ -35762,7 +35921,20 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
35762
35921
  feedEl.style.aspectRatio = "unset";
35763
35922
  feedEl.style.maxHeight = "none";
35764
35923
  document.body.appendChild(feedEl);
35924
+ feedEl.scrollTop = savedScrollTop;
35765
35925
  this.overlayEl.classList.remove("skw-active", "skw-feed-ready");
35926
+ let thumbOverlay = null;
35927
+ if (this._openThumbnailUrl) {
35928
+ const targetItemId2 = canTransfer ? this._openItemId : activeItemId;
35929
+ const targetEl = targetItemId2 && this.feedManager?.itemEls.get(targetItemId2);
35930
+ const videoContainer = targetEl?.querySelector('[data-ref="videoContainer"]');
35931
+ if (videoContainer) {
35932
+ thumbOverlay = document.createElement("img");
35933
+ thumbOverlay.src = this._openThumbnailUrl;
35934
+ thumbOverlay.style.cssText = "position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .4s cubic-bezier(.32,.72,0,1);pointer-events:none;z-index:2;";
35935
+ videoContainer.appendChild(thumbOverlay);
35936
+ }
35937
+ }
35766
35938
  const slotRadius = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--skw-radius").trim()) || 12;
35767
35939
  const scaleX = targetSlotRect.width / feedRect.width;
35768
35940
  const scaleY = targetSlotRect.height / feedRect.height;
@@ -35772,13 +35944,16 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
35772
35944
  feedEl.style.transformOrigin = "0 0";
35773
35945
  feedEl.style.overflow = "hidden";
35774
35946
  feedEl.style.scrollSnapType = "none";
35947
+ feedEl.style.pointerEvents = "none";
35775
35948
  feedEl.getBoundingClientRect();
35776
35949
  feedEl.classList.add("skw-flip-animating");
35777
35950
  requestAnimationFrame(() => {
35778
35951
  feedEl.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`;
35779
35952
  feedEl.style.borderRadius = `${endRadius}px`;
35953
+ if (thumbOverlay) thumbOverlay.style.opacity = "1";
35780
35954
  });
35781
35955
  await this._waitForTransitionEnd(feedEl, 450);
35956
+ if (thumbOverlay) thumbOverlay.remove();
35782
35957
  let transferVideo = null;
35783
35958
  let transferHls = null;
35784
35959
  if (canTransfer) {
@@ -35803,6 +35978,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
35803
35978
  feedEl.style.transformOrigin = "";
35804
35979
  feedEl.style.overflow = "";
35805
35980
  feedEl.style.scrollSnapType = "";
35981
+ feedEl.style.pointerEvents = "";
35806
35982
  feedEl.style.transform = "";
35807
35983
  feedEl.style.borderRadius = "";
35808
35984
  feedWrapper.insertBefore(feedEl, feedWrapper.firstChild);
@@ -35932,11 +36108,20 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
35932
36108
  }
35933
36109
  promoteToActive(itemId) {
35934
36110
  const hls = this.hlsInstances.get(itemId);
35935
- if (hls) {
35936
- hls.config.maxBufferLength = WidgetPlayerPool.ACTIVE_HLS_CONFIG.maxBufferLength;
35937
- hls.config.maxMaxBufferLength = WidgetPlayerPool.ACTIVE_HLS_CONFIG.maxMaxBufferLength;
35938
- hls.autoLevelCapping = -1;
35939
- hls.nextAutoLevel = -1;
36111
+ if (!hls) return;
36112
+ hls.config.maxBufferLength = WidgetPlayerPool.ACTIVE_HLS_CONFIG.maxBufferLength;
36113
+ hls.config.maxMaxBufferLength = WidgetPlayerPool.ACTIVE_HLS_CONFIG.maxMaxBufferLength;
36114
+ hls.autoLevelCapping = -1;
36115
+ const top = hls.levels ? hls.levels.length - 1 : -1;
36116
+ if (top > 0 && hls.currentLevel < top) {
36117
+ hls.currentLevel = top;
36118
+ const player = this.assignments.get(itemId);
36119
+ if (player) {
36120
+ player.currentTime = player.currentTime;
36121
+ }
36122
+ setTimeout(() => {
36123
+ if (!hls.destroyed) hls.currentLevel = -1;
36124
+ }, 4e3);
35940
36125
  }
35941
36126
  }
35942
36127
  _destroyHls(itemId) {
@@ -36541,7 +36726,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
36541
36726
 
36542
36727
  /* Video container */
36543
36728
  .sk-video-container{position:absolute;inset:0;width:100%;height:100%;overflow:hidden;background-size:cover;background-position:center}
36544
- .sk-video-container video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .15s ease}
36729
+ .sk-video-container video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .35s ease}
36545
36730
 
36546
36731
  /* Tap zone */
36547
36732
  .sk-tap-zone{position:absolute;inset:0;z-index:3;cursor:pointer}
@@ -36557,7 +36742,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
36557
36742
 
36558
36743
  /* Widget slot */
36559
36744
  .skw-slot{position:relative;overflow:hidden;cursor:pointer}
36560
- .skw-slot video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .2s ease}
36745
+ .skw-slot video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .4s ease}
36561
36746
  .skw-slot-thumb{position:absolute;inset:0;background-size:cover;background-position:center}
36562
36747
  .skw-slot-overlay{position:absolute;inset:0;z-index:4;pointer-events:none}
36563
36748
  .skw-slot-overlay>*{pointer-events:auto}