unified-video-framework 1.4.209 → 1.4.210

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 (27) hide show
  1. package/package.json +1 -1
  2. package/packages/core/dist/BasePlayer.d.ts +6 -5
  3. package/packages/core/dist/BasePlayer.d.ts.map +1 -1
  4. package/packages/core/dist/BasePlayer.js.map +1 -1
  5. package/packages/core/dist/VideoPlayer.js +1 -1
  6. package/packages/core/dist/VideoPlayer.js.map +1 -1
  7. package/packages/core/dist/interfaces/IVideoPlayer.d.ts +5 -3
  8. package/packages/core/dist/interfaces/IVideoPlayer.d.ts.map +1 -1
  9. package/packages/core/dist/interfaces.d.ts +26 -1
  10. package/packages/core/dist/interfaces.d.ts.map +1 -1
  11. package/packages/core/src/BasePlayer.ts +5 -5
  12. package/packages/core/src/VideoPlayer.ts +1 -1
  13. package/packages/core/src/interfaces/IVideoPlayer.ts +6 -3
  14. package/packages/core/src/interfaces.ts +60 -26
  15. package/packages/web/dist/WebPlayer.d.ts +8 -0
  16. package/packages/web/dist/WebPlayer.d.ts.map +1 -1
  17. package/packages/web/dist/WebPlayer.js +520 -32
  18. package/packages/web/dist/WebPlayer.js.map +1 -1
  19. package/packages/web/dist/react/components/EPGOverlay-improved-positioning.d.ts.map +1 -1
  20. package/packages/web/dist/react/components/EPGOverlay-improved-positioning.js +0 -8
  21. package/packages/web/dist/react/components/EPGOverlay-improved-positioning.js.map +1 -1
  22. package/packages/web/dist/react/components/EPGOverlay.d.ts.map +1 -1
  23. package/packages/web/dist/react/components/EPGOverlay.js +0 -8
  24. package/packages/web/dist/react/components/EPGOverlay.js.map +1 -1
  25. package/packages/web/src/WebPlayer.ts +611 -37
  26. package/packages/web/src/react/components/EPGOverlay-improved-positioning.tsx +0 -8
  27. package/packages/web/src/react/components/EPGOverlay.tsx +0 -8
@@ -3253,7 +3253,7 @@ export class WebPlayer extends BasePlayer {
3253
3253
  margin-left: 10px;
3254
3254
  }
3255
3255
 
3256
- /* Top Bar Container - Contains Title (left) and Controls (right) */
3256
+ /* Top Bar Container - Contains Navigation + Title (left) and Controls (right) */
3257
3257
  .uvf-top-bar {
3258
3258
  position: absolute;
3259
3259
  top: 0;
@@ -3270,16 +3270,97 @@ export class WebPlayer extends BasePlayer {
3270
3270
  transition: all 0.3s ease;
3271
3271
  }
3272
3272
 
3273
+ /* Left side container for navigation + title */
3274
+ .uvf-left-side {
3275
+ display: flex;
3276
+ align-items: center;
3277
+ gap: 12px;
3278
+ flex: 1;
3279
+ max-width: 70%;
3280
+ }
3281
+
3282
+ /* Navigation controls container */
3283
+ .uvf-navigation-controls {
3284
+ display: flex;
3285
+ align-items: center;
3286
+ gap: 8px;
3287
+ flex-shrink: 0;
3288
+ }
3289
+
3290
+ /* Navigation button styles */
3291
+ .uvf-nav-btn {
3292
+ width: 40px;
3293
+ height: 40px;
3294
+ min-width: 40px;
3295
+ min-height: 40px;
3296
+ border-radius: 50%;
3297
+ background: rgba(0, 0, 0, 0.6);
3298
+ border: 1px solid rgba(255, 255, 255, 0.2);
3299
+ color: white;
3300
+ cursor: pointer;
3301
+ display: flex;
3302
+ align-items: center;
3303
+ justify-content: center;
3304
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
3305
+ backdrop-filter: blur(8px);
3306
+ position: relative;
3307
+ overflow: hidden;
3308
+ }
3309
+
3310
+ .uvf-nav-btn:hover {
3311
+ background: rgba(255, 255, 255, 0.15);
3312
+ border-color: rgba(255, 255, 255, 0.4);
3313
+ transform: scale(1.05);
3314
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
3315
+ }
3316
+
3317
+ .uvf-nav-btn:active {
3318
+ transform: scale(0.95);
3319
+ }
3320
+
3321
+ .uvf-nav-btn svg {
3322
+ width: 20px;
3323
+ height: 20px;
3324
+ fill: currentColor;
3325
+ transition: all 0.2s ease;
3326
+ }
3327
+
3328
+ .uvf-nav-btn:hover svg {
3329
+ transform: scale(1.1);
3330
+ }
3331
+
3332
+ /* Back button specific styles */
3333
+ #uvf-back-btn {
3334
+ background: rgba(0, 0, 0, 0.7);
3335
+ }
3336
+
3337
+ #uvf-back-btn:hover {
3338
+ background: rgba(255, 255, 255, 0.2);
3339
+ border-color: var(--uvf-accent-1, #ff0000);
3340
+ }
3341
+
3342
+ /* Close button specific styles */
3343
+ #uvf-close-btn {
3344
+ background: rgba(220, 53, 69, 0.8);
3345
+ border-color: rgba(220, 53, 69, 0.6);
3346
+ }
3347
+
3348
+ #uvf-close-btn:hover {
3349
+ background: rgba(220, 53, 69, 1);
3350
+ border-color: rgba(220, 53, 69, 1);
3351
+ box-shadow: 0 4px 12px rgba(220, 53, 69, 0.4);
3352
+ }
3353
+
3273
3354
  .uvf-player-wrapper:hover .uvf-top-bar,
3274
3355
  .uvf-player-wrapper.controls-visible .uvf-top-bar {
3275
3356
  opacity: 1;
3276
3357
  transform: translateY(0);
3277
3358
  }
3278
3359
 
3279
- /* Title Bar - Left side of top bar */
3360
+ /* Title Bar - After navigation buttons */
3280
3361
  .uvf-title-bar {
3281
- flex: 0 1 auto;
3282
- max-width: 60%;
3362
+ flex: 1;
3363
+ min-width: 0; /* Allow shrinking */
3283
3364
  }
3284
3365
 
3285
3366
  /* Top Controls - Right side of top bar */
@@ -3294,33 +3375,112 @@ export class WebPlayer extends BasePlayer {
3294
3375
  .uvf-title-content {
3295
3376
  display: flex;
3296
3377
  align-items: center;
3297
- gap: 12px;
3378
+ width: 100%;
3379
+ min-width: 0; /* Allow shrinking */
3298
3380
  }
3299
- .uvf-video-thumb {
3300
- width: 56px;
3301
- height: 56px;
3302
- border-radius: 8px;
3303
- object-fit: cover;
3304
- box-shadow: 0 4px 14px rgba(0,0,0,0.5);
3305
- border: 1px solid rgba(255,255,255,0.25);
3306
- background: rgba(255,255,255,0.05);
3381
+
3382
+ .uvf-title-text {
3383
+ display: flex;
3384
+ flex-direction: column;
3385
+ min-width: 0; /* Allow shrinking */
3386
+ flex: 1;
3307
3387
  }
3308
- .uvf-title-text { display: flex; flex-direction: column; }
3388
+
3309
3389
  .uvf-video-title {
3310
3390
  color: var(--uvf-text-primary);
3311
- font-size: 18px;
3391
+ font-size: clamp(14px, 2.5vw, 18px); /* Responsive font size */
3312
3392
  font-weight: 600;
3313
3393
  text-shadow: 0 2px 4px rgba(0,0,0,0.5);
3394
+ line-height: 1.3;
3395
+ overflow: hidden;
3396
+ text-overflow: ellipsis;
3397
+ white-space: nowrap;
3398
+ max-width: 100%;
3399
+ cursor: pointer;
3400
+ transition: color 0.3s ease;
3401
+ position: relative;
3402
+ }
3403
+
3404
+ .uvf-video-title:hover {
3405
+ color: var(--uvf-accent-1, #ff0000);
3314
3406
  }
3315
3407
 
3316
3408
  .uvf-video-subtitle {
3317
3409
  color: var(--uvf-text-secondary);
3318
- font-size: 13px;
3319
- margin-top: 4px;
3320
- max-width: min(70vw, 900px);
3410
+ font-size: clamp(11px, 1.8vw, 13px); /* Responsive font size */
3411
+ margin-top: 2px;
3321
3412
  overflow: hidden;
3322
3413
  text-overflow: ellipsis;
3323
3414
  white-space: nowrap;
3415
+ max-width: 100%;
3416
+ opacity: 0.9;
3417
+ line-height: 1.4;
3418
+ cursor: pointer;
3419
+ transition: opacity 0.3s ease;
3420
+ position: relative;
3421
+ }
3422
+
3423
+ .uvf-video-subtitle:hover {
3424
+ opacity: 1;
3425
+ }
3426
+
3427
+ /* Tooltip for long text */
3428
+ .uvf-text-tooltip {
3429
+ position: absolute;
3430
+ bottom: 100%;
3431
+ left: 0;
3432
+ background: rgba(0, 0, 0, 0.9);
3433
+ color: white;
3434
+ padding: 8px 12px;
3435
+ border-radius: 6px;
3436
+ font-size: 13px;
3437
+ line-height: 1.4;
3438
+ max-width: 400px;
3439
+ word-wrap: break-word;
3440
+ white-space: normal;
3441
+ z-index: 1000;
3442
+ opacity: 0;
3443
+ visibility: hidden;
3444
+ transform: translateY(-5px);
3445
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
3446
+ pointer-events: none;
3447
+ border: 1px solid rgba(255, 255, 255, 0.2);
3448
+ backdrop-filter: blur(8px);
3449
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
3450
+ }
3451
+
3452
+ .uvf-text-tooltip::before {
3453
+ content: '';
3454
+ position: absolute;
3455
+ top: 100%;
3456
+ left: 12px;
3457
+ border: 5px solid transparent;
3458
+ border-top-color: rgba(0, 0, 0, 0.9);
3459
+ }
3460
+
3461
+ .uvf-text-tooltip.show {
3462
+ opacity: 1;
3463
+ visibility: visible;
3464
+ transform: translateY(0);
3465
+ }
3466
+
3467
+ /* Multi-line title option for desktop */
3468
+ .uvf-video-title.multiline {
3469
+ white-space: normal;
3470
+ display: -webkit-box;
3471
+ -webkit-line-clamp: 2;
3472
+ -webkit-box-orient: vertical;
3473
+ line-height: 1.2;
3474
+ max-height: 2.4em;
3475
+ }
3476
+
3477
+ .uvf-video-subtitle.multiline {
3478
+ white-space: normal;
3479
+ display: -webkit-box;
3480
+ -webkit-line-clamp: 3;
3481
+ -webkit-box-orient: vertical;
3482
+ line-height: 1.3;
3483
+ max-height: 3.9em;
3324
3484
  }
3325
3485
 
3326
3486
  /* Above seekbar section with time and branding */
@@ -3399,7 +3559,21 @@ export class WebPlayer extends BasePlayer {
3399
3559
  }
3400
3560
  }
3401
3561
 
3562
+ /* Ultra small screens */
3402
3563
  @media (max-width: 480px) {
3564
+ .uvf-video-title {
3565
+ font-size: clamp(11px, 3.5vw, 14px) !important;
3566
+ }
3567
+
3568
+ .uvf-video-subtitle {
3569
+ font-size: clamp(9px, 2.5vw, 11px) !important;
3570
+ -webkit-line-clamp: 1; /* Single line on very small screens */
3571
+ }
3572
+
3573
+ .uvf-left-side {
3574
+ max-width: 80%;
3575
+ }
3576
+
3403
3577
  .uvf-above-seekbar-section {
3404
3578
  margin-bottom: 5px;
3405
3579
  }
@@ -3787,6 +3961,47 @@ export class WebPlayer extends BasePlayer {
3787
3961
  }
3788
3962
 
3789
3963
 
3964
+ /* Mobile responsive styles for navigation buttons */
3965
+ @media screen and (max-width: 767px) {
3966
+ .uvf-nav-btn {
3967
+ width: 36px;
3968
+ height: 36px;
3969
+ min-width: 36px;
3970
+ min-height: 36px;
3971
+ }
3972
+
3973
+ .uvf-nav-btn svg {
3974
+ width: 18px;
3975
+ height: 18px;
3976
+ }
3977
+
3978
+ .uvf-navigation-controls {
3979
+ gap: 6px;
3980
+ }
3981
+
3982
+ .uvf-left-side {
3983
+ gap: 8px;
3984
+ max-width: 75%;
3985
+ }
3986
+
3987
+ /* Mobile title adjustments */
3988
+ .uvf-video-title {
3989
+ font-size: clamp(12px, 3vw, 16px) !important;
3990
+ line-height: 1.2;
3991
+ }
3992
+
3993
+ .uvf-video-subtitle {
3994
+ font-size: clamp(10px, 2.2vw, 12px) !important;
3995
+ margin-top: 1px;
3996
+ /* Allow wrapping on mobile if needed */
3997
+ white-space: normal;
3998
+ display: -webkit-box;
3999
+ -webkit-line-clamp: 2;
4000
+ -webkit-box-orient: vertical;
4001
+ overflow: hidden;
4002
+ }
4003
+ }
4004
+
3790
4005
  /* Mobile portrait - hide skip buttons, ensure top bar visible */
3791
4006
  @media screen and (max-width: 767px) and (orientation: portrait) {
3792
4007
  #uvf-skip-back,
@@ -3954,6 +4169,31 @@ export class WebPlayer extends BasePlayer {
3954
4169
 
3955
4170
  /* Tablet devices - Enhanced UX with desktop features */
3956
4171
  @media screen and (min-width: 768px) and (max-width: 1023px) {
4172
+ /* Tablet navigation and title adjustments */
4173
+ .uvf-nav-btn {
4174
+ width: 38px;
4175
+ height: 38px;
4176
+ min-width: 38px;
4177
+ min-height: 38px;
4178
+ }
4179
+
4180
+ .uvf-nav-btn svg {
4181
+ width: 19px;
4182
+ height: 19px;
4183
+ }
4184
+
4185
+ .uvf-left-side {
4186
+ max-width: 70%;
4187
+ }
4188
+
4189
+ .uvf-video-title {
4190
+ font-size: clamp(15px, 2.2vw, 17px) !important;
4191
+ }
4192
+
4193
+ .uvf-video-subtitle {
4194
+ font-size: clamp(12px, 1.8vw, 13px) !important;
4195
+ }
4196
+
3957
4197
  .uvf-controls-bar {
3958
4198
  padding: 18px 16px;
3959
4199
  background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 70%, var(--uvf-overlay-transparent) 100%);
@@ -4486,6 +4726,40 @@ export class WebPlayer extends BasePlayer {
4486
4726
  }
4487
4727
  }
4488
4728
 
4729
+ /* Desktop styles for title and navigation */
4730
+ @media screen and (min-width: 1024px) {
4731
+ .uvf-left-side {
4732
+ max-width: 65%; /* More space for title on desktop */
4733
+ }
4734
+
4735
+ .uvf-video-title {
4736
+ font-size: clamp(16px, 1.8vw, 20px) !important;
4737
+ font-weight: 700; /* Bolder on desktop */
4738
+ }
4739
+
4740
+ .uvf-video-subtitle {
4741
+ font-size: clamp(13px, 1.4vw, 15px) !important;
4742
+ margin-top: 3px;
4743
+ }
4744
+
4745
+ /* Allow hover effects on desktop */
4746
+ .uvf-title-bar:hover .uvf-video-title {
4747
+ color: var(--uvf-accent-1, #ff0000);
4748
+ transition: color 0.3s ease;
4749
+ }
4750
+ }
4751
+
4752
+ /* Ultra-wide screens */
4753
+ @media screen and (min-width: 1440px) {
4754
+ .uvf-video-title {
4755
+ font-size: clamp(18px, 1.6vw, 22px) !important;
4756
+ }
4757
+
4758
+ .uvf-video-subtitle {
4759
+ font-size: clamp(14px, 1.2vw, 16px) !important;
4760
+ }
4761
+ }
4762
+
4489
4763
  /* Paywall Desktop */
4490
4764
  @media screen and (min-width: 1024px) {
4491
4765
  .uvf-paywall-modal {
@@ -4544,6 +4818,100 @@ export class WebPlayer extends BasePlayer {
4544
4818
  container.appendChild(brandingContainer);
4545
4819
  this.debugLog('Framework branding added');
4546
4820
  }
4821
+ createNavigationButtons(container) {
4822
+ const navigationConfig = this.config.navigation;
4823
+ if (!navigationConfig)
4824
+ return;
4825
+ const { backButton, closeButton } = navigationConfig;
4826
+ if (backButton?.enabled) {
4827
+ const backBtn = document.createElement('button');
4828
+ backBtn.className = 'uvf-control-btn uvf-nav-btn';
4829
+ backBtn.id = 'uvf-back-btn';
4830
+ backBtn.title = backButton.title || 'Back';
4831
+ backBtn.setAttribute('aria-label', backButton.ariaLabel || 'Go back');
4832
+ const backIcon = this.getNavigationIcon(backButton.icon || 'arrow', backButton.customIcon);
4833
+ backBtn.innerHTML = backIcon;
4834
+ backBtn.addEventListener('click', async (e) => {
4835
+ e.preventDefault();
4836
+ e.stopPropagation();
4837
+ if (backButton.onClick) {
4838
+ await backButton.onClick();
4839
+ }
4840
+ else if (backButton.href) {
4841
+ if (backButton.replace) {
4842
+ window.history.replaceState(null, '', backButton.href);
4843
+ }
4844
+ else {
4845
+ window.location.href = backButton.href;
4846
+ }
4847
+ }
4848
+ else {
4849
+ window.history.back();
4850
+ }
4851
+ this.emit('navigationBackClicked');
4852
+ });
4853
+ container.appendChild(backBtn);
4854
+ }
4855
+ if (closeButton?.enabled) {
4856
+ const closeBtn = document.createElement('button');
4857
+ closeBtn.className = 'uvf-control-btn uvf-nav-btn';
4858
+ closeBtn.id = 'uvf-close-btn';
4859
+ closeBtn.title = closeButton.title || 'Close';
4860
+ closeBtn.setAttribute('aria-label', closeButton.ariaLabel || 'Close player');
4861
+ const closeIcon = this.getNavigationIcon(closeButton.icon || 'x', closeButton.customIcon);
4862
+ closeBtn.innerHTML = closeIcon;
4863
+ closeBtn.addEventListener('click', async (e) => {
4864
+ e.preventDefault();
4865
+ e.stopPropagation();
4866
+ if (closeButton.onClick) {
4867
+ await closeButton.onClick();
4868
+ }
4869
+ else {
4870
+ if (closeButton.exitFullscreen && this.isFullscreen()) {
4871
+ await this.exitFullscreen();
4872
+ }
4873
+ if (closeButton.closeModal) {
4874
+ const playerWrapper = this.container?.querySelector('.uvf-player-wrapper');
4875
+ if (playerWrapper) {
4876
+ playerWrapper.style.display = 'none';
4877
+ }
4878
+ }
4879
+ }
4880
+ this.emit('navigationCloseClicked');
4881
+ });
4882
+ container.appendChild(closeBtn);
4883
+ }
4884
+ }
4885
+ getNavigationIcon(iconType, customIcon) {
4886
+ if (customIcon) {
4887
+ if (customIcon.startsWith('http') || customIcon.includes('.')) {
4888
+ return `<img src="${customIcon}" alt="" style="width: 20px; height: 20px;" />`;
4889
+ }
4890
+ return customIcon;
4891
+ }
4892
+ switch (iconType) {
4893
+ case 'arrow':
4894
+ return `<svg viewBox="0 0 24 24">
4895
+ <path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.42-1.41L7.83 13H20v-2z" fill="currentColor"/>
4896
+ </svg>`;
4897
+ case 'chevron':
4898
+ return `<svg viewBox="0 0 24 24">
4899
+ <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" fill="currentColor"/>
4900
+ </svg>`;
4901
+ case 'x':
4902
+ return `<svg viewBox="0 0 24 24">
4903
+ <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" fill="currentColor"/>
4904
+ </svg>`;
4905
+ case 'close':
4906
+ return `<svg viewBox="0 0 24 24">
4907
+ <path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z" fill="currentColor"/>
4908
+ </svg>`;
4909
+ default:
4910
+ return `<svg viewBox="0 0 24 24">
4911
+ <path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.42-1.41L7.83 13H20v-2z" fill="currentColor"/>
4912
+ </svg>`;
4913
+ }
4914
+ }
4547
4915
  createCustomControls(container) {
4548
4916
  const topGradient = document.createElement('div');
4549
4917
  topGradient.className = 'uvf-top-gradient';
@@ -4553,18 +4921,24 @@ export class WebPlayer extends BasePlayer {
4553
4921
  container.appendChild(controlsGradient);
4554
4922
  const topBar = document.createElement('div');
4555
4923
  topBar.className = 'uvf-top-bar';
4924
+ const leftSide = document.createElement('div');
4925
+ leftSide.className = 'uvf-left-side';
4926
+ const navigationControls = document.createElement('div');
4927
+ navigationControls.className = 'uvf-navigation-controls';
4928
+ this.createNavigationButtons(navigationControls);
4929
+ leftSide.appendChild(navigationControls);
4556
4930
  const titleBar = document.createElement('div');
4557
4931
  titleBar.className = 'uvf-title-bar';
4558
4932
  titleBar.innerHTML = `
4559
4933
  <div class="uvf-title-content">
4560
- <img class="uvf-video-thumb" id="uvf-video-thumb" alt="thumbnail" style="display:none;" />
4561
4934
  <div class="uvf-title-text">
4562
4935
  <div class=\"uvf-video-title\" id=\"uvf-video-title\" style=\"display:none;\"></div>
4563
4936
  <div class=\"uvf-video-subtitle\" id=\"uvf-video-description\" style=\"display:none;\"></div>
4564
4937
  </div>
4565
4938
  </div>
4566
4939
  `;
4567
- topBar.appendChild(titleBar);
4940
+ leftSide.appendChild(titleBar);
4941
+ topBar.appendChild(leftSide);
4568
4942
  const topControls = document.createElement('div');
4569
4943
  topControls.className = 'uvf-top-controls';
4570
4944
  topControls.innerHTML = `
@@ -5225,11 +5599,13 @@ export class WebPlayer extends BasePlayer {
5225
5599
  break;
5226
5600
  case 'ArrowLeft':
5227
5601
  e.preventDefault();
5602
+ e.stopImmediatePropagation();
5228
5603
  this.seek(Math.max(0, this.video.currentTime - 10));
5229
5604
  shortcutText = '-10s';
5230
5605
  break;
5231
5606
  case 'ArrowRight':
5232
5607
  e.preventDefault();
5608
+ e.stopImmediatePropagation();
5233
5609
  this.seek(Math.min(this.video.duration, this.video.currentTime + 10));
5234
5610
  shortcutText = '+10s';
5235
5611
  break;
@@ -6210,7 +6586,7 @@ export class WebPlayer extends BasePlayer {
6210
6586
  autoSkip: this.chapterConfig.userPreferences?.autoSkipIntro || false,
6211
6587
  onChapterChange: (chapter) => {
6212
6588
  this.debugLog('Core chapter changed:', chapter?.title || 'none');
6213
- this.emit('chapterchange', chapter);
6589
+ this.emit('onChapterchange', chapter);
6214
6590
  },
6215
6591
  onSegmentEntered: (segment) => {
6216
6592
  this.debugLog('Core segment entered:', segment.title);
@@ -7323,6 +7699,123 @@ export class WebPlayer extends BasePlayer {
7323
7699
  this.showNotification('Share failed');
7324
7700
  }
7325
7701
  }
7702
+ isTextTruncated(element) {
7703
+ return element.scrollWidth > element.offsetWidth || element.scrollHeight > element.offsetHeight;
7704
+ }
7705
+ showTextTooltip(element, text) {
7706
+ const existingTooltip = element.querySelector('.uvf-text-tooltip');
7707
+ if (existingTooltip) {
7708
+ existingTooltip.remove();
7709
+ }
7710
+ const tooltip = document.createElement('div');
7711
+ tooltip.className = 'uvf-text-tooltip';
7712
+ tooltip.textContent = text;
7713
+ element.appendChild(tooltip);
7714
+ setTimeout(() => {
7715
+ tooltip.classList.add('show');
7716
+ }, 100);
7717
+ }
7718
+ hideTextTooltip(element) {
7719
+ const tooltip = element.querySelector('.uvf-text-tooltip');
7720
+ if (tooltip) {
7721
+ tooltip.classList.remove('show');
7722
+ setTimeout(() => {
7723
+ if (tooltip.parentElement) {
7724
+ tooltip.remove();
7725
+ }
7726
+ }, 300);
7727
+ }
7728
+ }
7729
+ setupTextTooltips() {
7730
+ const titleElement = document.getElementById('uvf-video-title');
7731
+ const descElement = document.getElementById('uvf-video-description');
7732
+ if (titleElement) {
7733
+ titleElement.addEventListener('mouseenter', () => {
7734
+ const titleText = (this.source?.metadata?.title || '').toString().trim();
7735
+ if (this.isTextTruncated(titleElement) && titleText) {
7736
+ this.showTextTooltip(titleElement, titleText);
7737
+ }
7738
+ });
7739
+ titleElement.addEventListener('mouseleave', () => {
7740
+ this.hideTextTooltip(titleElement);
7741
+ });
7742
+ titleElement.addEventListener('touchstart', () => {
7743
+ const titleText = (this.source?.metadata?.title || '').toString().trim();
7744
+ if (this.isTextTruncated(titleElement) && titleText) {
7745
+ this.showTextTooltip(titleElement, titleText);
7746
+ setTimeout(() => {
7747
+ this.hideTextTooltip(titleElement);
7748
+ }, 3000);
7749
+ }
7750
+ });
7751
+ }
7752
+ if (descElement) {
7753
+ descElement.addEventListener('mouseenter', () => {
7754
+ const descText = (this.source?.metadata?.description || '').toString().trim();
7755
+ if (this.isTextTruncated(descElement) && descText) {
7756
+ this.showTextTooltip(descElement, descText);
7757
+ }
7758
+ });
7759
+ descElement.addEventListener('mouseleave', () => {
7760
+ this.hideTextTooltip(descElement);
7761
+ });
7762
+ descElement.addEventListener('touchstart', () => {
7763
+ const descText = (this.source?.metadata?.description || '').toString().trim();
7764
+ if (this.isTextTruncated(descElement) && descText) {
7765
+ this.showTextTooltip(descElement, descText);
7766
+ setTimeout(() => {
7767
+ this.hideTextTooltip(descElement);
7768
+ }, 3000);
7769
+ }
7770
+ });
7771
+ }
7772
+ }
7773
+ smartTruncateText(text, maxWords = 12) {
7774
+ const words = text.split(' ');
7775
+ if (words.length <= maxWords) {
7776
+ return { truncated: text, needsTooltip: false };
7777
+ }
7778
+ const truncated = words.slice(0, maxWords).join(' ') + '...';
7779
+ return { truncated, needsTooltip: true };
7780
+ }
7781
+ applySmartTextDisplay(titleEl, descEl, titleText, descText) {
7782
+ const isDesktop = window.innerWidth >= 1024;
7783
+ const isMobile = window.innerWidth < 768;
7784
+ if (titleEl && titleText) {
7785
+ const wordCount = titleText.split(' ').length;
7786
+ if (isDesktop && wordCount > 8 && wordCount <= 15) {
7787
+ titleEl.classList.add('multiline');
7788
+ titleEl.textContent = titleText;
7789
+ }
7790
+ else if (wordCount > 12) {
7791
+ const maxWords = isMobile ? 8 : isDesktop ? 12 : 10;
7792
+ const { truncated } = this.smartTruncateText(titleText, maxWords);
7793
+ titleEl.textContent = truncated;
7794
+ titleEl.classList.remove('multiline');
7795
+ }
7796
+ else {
7797
+ titleEl.textContent = titleText;
7798
+ titleEl.classList.remove('multiline');
7799
+ }
7800
+ }
7801
+ if (descEl && descText) {
7802
+ const wordCount = descText.split(' ').length;
7803
+ if (isDesktop && wordCount > 15 && wordCount <= 25) {
7804
+ descEl.classList.add('multiline');
7805
+ descEl.textContent = descText;
7806
+ }
7807
+ else if (wordCount > 20) {
7808
+ const maxWords = isMobile ? 12 : isDesktop ? 18 : 15;
7809
+ const { truncated } = this.smartTruncateText(descText, maxWords);
7810
+ descEl.textContent = truncated;
7811
+ descEl.classList.remove('multiline');
7812
+ }
7813
+ else {
7814
+ descEl.textContent = descText;
7815
+ descEl.classList.remove('multiline');
7816
+ }
7817
+ }
7818
+ }
7326
7819
  updateMetadataUI() {
7327
7820
  try {
7328
7821
  const md = this.source?.metadata || {};
@@ -7333,28 +7826,23 @@ export class WebPlayer extends BasePlayer {
7333
7826
  const titleText = (md.title || '').toString().trim();
7334
7827
  const descText = (md.description || '').toString().trim();
7335
7828
  const thumbUrl = (md.thumbnailUrl || '').toString().trim();
7829
+ this.applySmartTextDisplay(titleEl, descEl, titleText, descText);
7336
7830
  if (titleEl) {
7337
- titleEl.textContent = titleText;
7338
7831
  titleEl.style.display = titleText ? 'block' : 'none';
7339
7832
  }
7340
7833
  if (descEl) {
7341
- descEl.textContent = descText;
7342
7834
  descEl.style.display = descText ? 'block' : 'none';
7343
7835
  }
7344
7836
  if (thumbEl) {
7345
- if (thumbUrl) {
7346
- thumbEl.src = thumbUrl;
7347
- thumbEl.style.display = 'block';
7348
- }
7349
- else {
7350
- thumbEl.removeAttribute('src');
7351
- thumbEl.style.display = 'none';
7352
- }
7837
+ thumbEl.style.display = 'none';
7353
7838
  }
7354
- const hasAny = !!(titleText || descText || thumbUrl);
7839
+ const hasAny = !!(titleText || descText);
7355
7840
  if (titleBar) {
7356
7841
  titleBar.style.display = hasAny ? '' : 'none';
7357
7842
  }
7843
+ setTimeout(() => {
7844
+ this.setupTextTooltips();
7845
+ }, 100);
7358
7846
  }
7359
7847
  catch (_) { }
7360
7848
  }