unified-video-framework 1.4.208 → 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.
- package/package.json +1 -1
- package/packages/core/dist/BasePlayer.d.ts +6 -5
- package/packages/core/dist/BasePlayer.d.ts.map +1 -1
- package/packages/core/dist/BasePlayer.js.map +1 -1
- package/packages/core/dist/VideoPlayer.js +1 -1
- package/packages/core/dist/VideoPlayer.js.map +1 -1
- package/packages/core/dist/interfaces/IVideoPlayer.d.ts +5 -3
- package/packages/core/dist/interfaces/IVideoPlayer.d.ts.map +1 -1
- package/packages/core/dist/interfaces.d.ts +26 -1
- package/packages/core/dist/interfaces.d.ts.map +1 -1
- package/packages/core/src/BasePlayer.ts +5 -5
- package/packages/core/src/VideoPlayer.ts +1 -1
- package/packages/core/src/interfaces/IVideoPlayer.ts +6 -3
- package/packages/core/src/interfaces.ts +60 -26
- package/packages/web/dist/WebPlayer.d.ts +8 -0
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +557 -36
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/dist/react/components/EPGOverlay-improved-positioning.d.ts.map +1 -1
- package/packages/web/dist/react/components/EPGOverlay-improved-positioning.js +0 -8
- package/packages/web/dist/react/components/EPGOverlay-improved-positioning.js.map +1 -1
- package/packages/web/dist/react/components/EPGOverlay.d.ts.map +1 -1
- package/packages/web/dist/react/components/EPGOverlay.js +0 -8
- package/packages/web/dist/react/components/EPGOverlay.js.map +1 -1
- package/packages/web/src/WebPlayer.ts +651 -41
- package/packages/web/src/react/components/EPGOverlay-improved-positioning.tsx +0 -8
- package/packages/web/src/react/components/EPGOverlay.tsx +0 -8
|
@@ -1142,7 +1142,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
1142
1142
|
private setupClickToUnmute(): void {
|
|
1143
1143
|
// Remove any existing listener first
|
|
1144
1144
|
if (this.clickToUnmuteHandler) {
|
|
1145
|
-
this.playerWrapper?.removeEventListener('click', this.clickToUnmuteHandler);
|
|
1145
|
+
this.playerWrapper?.removeEventListener('click', this.clickToUnmuteHandler, true);
|
|
1146
1146
|
}
|
|
1147
1147
|
|
|
1148
1148
|
this.clickToUnmuteHandler = (e: MouseEvent) => {
|
|
@@ -1159,6 +1159,10 @@ export class WebPlayer extends BasePlayer {
|
|
|
1159
1159
|
return;
|
|
1160
1160
|
}
|
|
1161
1161
|
|
|
1162
|
+
// Stop the event from triggering play/pause
|
|
1163
|
+
e.stopPropagation();
|
|
1164
|
+
e.preventDefault();
|
|
1165
|
+
|
|
1162
1166
|
// Unmute the video
|
|
1163
1167
|
this.video.muted = false;
|
|
1164
1168
|
this.debugLog('🔊 Video unmuted by clicking on player');
|
|
@@ -1166,12 +1170,13 @@ export class WebPlayer extends BasePlayer {
|
|
|
1166
1170
|
|
|
1167
1171
|
// Clean up the handler
|
|
1168
1172
|
if (this.clickToUnmuteHandler) {
|
|
1169
|
-
this.playerWrapper?.removeEventListener('click', this.clickToUnmuteHandler);
|
|
1173
|
+
this.playerWrapper?.removeEventListener('click', this.clickToUnmuteHandler, true);
|
|
1170
1174
|
this.clickToUnmuteHandler = null;
|
|
1171
1175
|
}
|
|
1172
1176
|
};
|
|
1173
1177
|
|
|
1174
|
-
|
|
1178
|
+
// Use capture phase to intercept clicks before they reach the video element
|
|
1179
|
+
this.playerWrapper?.addEventListener('click', this.clickToUnmuteHandler, true);
|
|
1175
1180
|
this.debugLog('👆 Click anywhere to unmute enabled');
|
|
1176
1181
|
}
|
|
1177
1182
|
|
|
@@ -1189,7 +1194,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
1189
1194
|
|
|
1190
1195
|
// Remove click to unmute handler when button is hidden
|
|
1191
1196
|
if (this.clickToUnmuteHandler) {
|
|
1192
|
-
this.playerWrapper?.removeEventListener('click', this.clickToUnmuteHandler);
|
|
1197
|
+
this.playerWrapper?.removeEventListener('click', this.clickToUnmuteHandler, true);
|
|
1193
1198
|
this.clickToUnmuteHandler = null;
|
|
1194
1199
|
}
|
|
1195
1200
|
}
|
|
@@ -3211,6 +3216,37 @@ export class WebPlayer extends BasePlayer {
|
|
|
3211
3216
|
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
|
|
3212
3217
|
}
|
|
3213
3218
|
|
|
3219
|
+
/* PiP Button Specific Styling */
|
|
3220
|
+
#uvf-pip-btn {
|
|
3221
|
+
background: var(--uvf-button-bg);
|
|
3222
|
+
border: 1px solid var(--uvf-button-border);
|
|
3223
|
+
position: relative;
|
|
3224
|
+
z-index: 10;
|
|
3225
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
#uvf-pip-btn:hover {
|
|
3229
|
+
transform: scale(1.08);
|
|
3230
|
+
box-shadow: 0 4px 12px var(--uvf-button-shadow);
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
#uvf-pip-btn:active {
|
|
3234
|
+
transform: scale(0.95);
|
|
3235
|
+
transition: all 0.1s ease;
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3238
|
+
#uvf-pip-btn svg {
|
|
3239
|
+
opacity: 0.9;
|
|
3240
|
+
transition: all 0.3s ease;
|
|
3241
|
+
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
#uvf-pip-btn:hover svg {
|
|
3245
|
+
opacity: 1;
|
|
3246
|
+
transform: scale(1.05);
|
|
3247
|
+
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3214
3250
|
/* Fullscreen Button Specific Styling */
|
|
3215
3251
|
#uvf-fullscreen-btn {
|
|
3216
3252
|
background: var(--uvf-button-bg);
|
|
@@ -3714,7 +3750,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
3714
3750
|
margin-left: 10px;
|
|
3715
3751
|
}
|
|
3716
3752
|
|
|
3717
|
-
/* Top Bar Container - Contains Title (left) and Controls (right) */
|
|
3753
|
+
/* Top Bar Container - Contains Navigation + Title (left) and Controls (right) */
|
|
3718
3754
|
.uvf-top-bar {
|
|
3719
3755
|
position: absolute;
|
|
3720
3756
|
top: 0;
|
|
@@ -3731,16 +3767,97 @@ export class WebPlayer extends BasePlayer {
|
|
|
3731
3767
|
transition: all 0.3s ease;
|
|
3732
3768
|
}
|
|
3733
3769
|
|
|
3770
|
+
/* Left side container for navigation + title */
|
|
3771
|
+
.uvf-left-side {
|
|
3772
|
+
display: flex;
|
|
3773
|
+
align-items: center;
|
|
3774
|
+
gap: 12px;
|
|
3775
|
+
flex: 1;
|
|
3776
|
+
max-width: 70%;
|
|
3777
|
+
}
|
|
3778
|
+
|
|
3779
|
+
/* Navigation controls container */
|
|
3780
|
+
.uvf-navigation-controls {
|
|
3781
|
+
display: flex;
|
|
3782
|
+
align-items: center;
|
|
3783
|
+
gap: 8px;
|
|
3784
|
+
flex-shrink: 0;
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
/* Navigation button styles */
|
|
3788
|
+
.uvf-nav-btn {
|
|
3789
|
+
width: 40px;
|
|
3790
|
+
height: 40px;
|
|
3791
|
+
min-width: 40px;
|
|
3792
|
+
min-height: 40px;
|
|
3793
|
+
border-radius: 50%;
|
|
3794
|
+
background: rgba(0, 0, 0, 0.6);
|
|
3795
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
3796
|
+
color: white;
|
|
3797
|
+
cursor: pointer;
|
|
3798
|
+
display: flex;
|
|
3799
|
+
align-items: center;
|
|
3800
|
+
justify-content: center;
|
|
3801
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
3802
|
+
backdrop-filter: blur(8px);
|
|
3803
|
+
position: relative;
|
|
3804
|
+
overflow: hidden;
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
.uvf-nav-btn:hover {
|
|
3808
|
+
background: rgba(255, 255, 255, 0.15);
|
|
3809
|
+
border-color: rgba(255, 255, 255, 0.4);
|
|
3810
|
+
transform: scale(1.05);
|
|
3811
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
.uvf-nav-btn:active {
|
|
3815
|
+
transform: scale(0.95);
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3818
|
+
.uvf-nav-btn svg {
|
|
3819
|
+
width: 20px;
|
|
3820
|
+
height: 20px;
|
|
3821
|
+
fill: currentColor;
|
|
3822
|
+
transition: all 0.2s ease;
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
.uvf-nav-btn:hover svg {
|
|
3826
|
+
transform: scale(1.1);
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
/* Back button specific styles */
|
|
3830
|
+
#uvf-back-btn {
|
|
3831
|
+
background: rgba(0, 0, 0, 0.7);
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
#uvf-back-btn:hover {
|
|
3835
|
+
background: rgba(255, 255, 255, 0.2);
|
|
3836
|
+
border-color: var(--uvf-accent-1, #ff0000);
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
/* Close button specific styles */
|
|
3840
|
+
#uvf-close-btn {
|
|
3841
|
+
background: rgba(220, 53, 69, 0.8);
|
|
3842
|
+
border-color: rgba(220, 53, 69, 0.6);
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
#uvf-close-btn:hover {
|
|
3846
|
+
background: rgba(220, 53, 69, 1);
|
|
3847
|
+
border-color: rgba(220, 53, 69, 1);
|
|
3848
|
+
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.4);
|
|
3849
|
+
}
|
|
3850
|
+
|
|
3734
3851
|
.uvf-player-wrapper:hover .uvf-top-bar,
|
|
3735
3852
|
.uvf-player-wrapper.controls-visible .uvf-top-bar {
|
|
3736
3853
|
opacity: 1;
|
|
3737
3854
|
transform: translateY(0);
|
|
3738
3855
|
}
|
|
3739
3856
|
|
|
3740
|
-
/* Title Bar -
|
|
3857
|
+
/* Title Bar - After navigation buttons */
|
|
3741
3858
|
.uvf-title-bar {
|
|
3742
|
-
flex:
|
|
3743
|
-
|
|
3859
|
+
flex: 1;
|
|
3860
|
+
min-width: 0; /* Allow shrinking */
|
|
3744
3861
|
}
|
|
3745
3862
|
|
|
3746
3863
|
/* Top Controls - Right side of top bar */
|
|
@@ -3755,33 +3872,112 @@ export class WebPlayer extends BasePlayer {
|
|
|
3755
3872
|
.uvf-title-content {
|
|
3756
3873
|
display: flex;
|
|
3757
3874
|
align-items: center;
|
|
3758
|
-
|
|
3875
|
+
width: 100%;
|
|
3876
|
+
min-width: 0; /* Allow shrinking */
|
|
3759
3877
|
}
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
border: 1px solid rgba(255,255,255,0.25);
|
|
3767
|
-
background: rgba(255,255,255,0.05);
|
|
3878
|
+
|
|
3879
|
+
.uvf-title-text {
|
|
3880
|
+
display: flex;
|
|
3881
|
+
flex-direction: column;
|
|
3882
|
+
min-width: 0; /* Allow shrinking */
|
|
3883
|
+
flex: 1;
|
|
3768
3884
|
}
|
|
3769
|
-
|
|
3885
|
+
|
|
3770
3886
|
.uvf-video-title {
|
|
3771
3887
|
color: var(--uvf-text-primary);
|
|
3772
|
-
font-size: 18px;
|
|
3888
|
+
font-size: clamp(14px, 2.5vw, 18px); /* Responsive font size */
|
|
3773
3889
|
font-weight: 600;
|
|
3774
3890
|
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
|
|
3891
|
+
line-height: 1.3;
|
|
3892
|
+
overflow: hidden;
|
|
3893
|
+
text-overflow: ellipsis;
|
|
3894
|
+
white-space: nowrap;
|
|
3895
|
+
max-width: 100%;
|
|
3896
|
+
cursor: pointer;
|
|
3897
|
+
transition: color 0.3s ease;
|
|
3898
|
+
position: relative;
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3901
|
+
.uvf-video-title:hover {
|
|
3902
|
+
color: var(--uvf-accent-1, #ff0000);
|
|
3775
3903
|
}
|
|
3776
3904
|
|
|
3777
3905
|
.uvf-video-subtitle {
|
|
3778
3906
|
color: var(--uvf-text-secondary);
|
|
3779
|
-
font-size: 13px;
|
|
3780
|
-
margin-top:
|
|
3781
|
-
max-width: min(70vw, 900px);
|
|
3907
|
+
font-size: clamp(11px, 1.8vw, 13px); /* Responsive font size */
|
|
3908
|
+
margin-top: 2px;
|
|
3782
3909
|
overflow: hidden;
|
|
3783
3910
|
text-overflow: ellipsis;
|
|
3784
3911
|
white-space: nowrap;
|
|
3912
|
+
max-width: 100%;
|
|
3913
|
+
opacity: 0.9;
|
|
3914
|
+
line-height: 1.4;
|
|
3915
|
+
cursor: pointer;
|
|
3916
|
+
transition: opacity 0.3s ease;
|
|
3917
|
+
position: relative;
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3920
|
+
.uvf-video-subtitle:hover {
|
|
3921
|
+
opacity: 1;
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3924
|
+
/* Tooltip for long text */
|
|
3925
|
+
.uvf-text-tooltip {
|
|
3926
|
+
position: absolute;
|
|
3927
|
+
bottom: 100%;
|
|
3928
|
+
left: 0;
|
|
3929
|
+
background: rgba(0, 0, 0, 0.9);
|
|
3930
|
+
color: white;
|
|
3931
|
+
padding: 8px 12px;
|
|
3932
|
+
border-radius: 6px;
|
|
3933
|
+
font-size: 13px;
|
|
3934
|
+
line-height: 1.4;
|
|
3935
|
+
max-width: 400px;
|
|
3936
|
+
word-wrap: break-word;
|
|
3937
|
+
white-space: normal;
|
|
3938
|
+
z-index: 1000;
|
|
3939
|
+
opacity: 0;
|
|
3940
|
+
visibility: hidden;
|
|
3941
|
+
transform: translateY(-5px);
|
|
3942
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
3943
|
+
pointer-events: none;
|
|
3944
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
3945
|
+
backdrop-filter: blur(8px);
|
|
3946
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
.uvf-text-tooltip::before {
|
|
3950
|
+
content: '';
|
|
3951
|
+
position: absolute;
|
|
3952
|
+
top: 100%;
|
|
3953
|
+
left: 12px;
|
|
3954
|
+
border: 5px solid transparent;
|
|
3955
|
+
border-top-color: rgba(0, 0, 0, 0.9);
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
.uvf-text-tooltip.show {
|
|
3959
|
+
opacity: 1;
|
|
3960
|
+
visibility: visible;
|
|
3961
|
+
transform: translateY(0);
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
/* Multi-line title option for desktop */
|
|
3965
|
+
.uvf-video-title.multiline {
|
|
3966
|
+
white-space: normal;
|
|
3967
|
+
display: -webkit-box;
|
|
3968
|
+
-webkit-line-clamp: 2;
|
|
3969
|
+
-webkit-box-orient: vertical;
|
|
3970
|
+
line-height: 1.2;
|
|
3971
|
+
max-height: 2.4em;
|
|
3972
|
+
}
|
|
3973
|
+
|
|
3974
|
+
.uvf-video-subtitle.multiline {
|
|
3975
|
+
white-space: normal;
|
|
3976
|
+
display: -webkit-box;
|
|
3977
|
+
-webkit-line-clamp: 3;
|
|
3978
|
+
-webkit-box-orient: vertical;
|
|
3979
|
+
line-height: 1.3;
|
|
3980
|
+
max-height: 3.9em;
|
|
3785
3981
|
}
|
|
3786
3982
|
|
|
3787
3983
|
/* Above seekbar section with time and branding */
|
|
@@ -3860,7 +4056,21 @@ export class WebPlayer extends BasePlayer {
|
|
|
3860
4056
|
}
|
|
3861
4057
|
}
|
|
3862
4058
|
|
|
4059
|
+
/* Ultra small screens */
|
|
3863
4060
|
@media (max-width: 480px) {
|
|
4061
|
+
.uvf-video-title {
|
|
4062
|
+
font-size: clamp(11px, 3.5vw, 14px) !important;
|
|
4063
|
+
}
|
|
4064
|
+
|
|
4065
|
+
.uvf-video-subtitle {
|
|
4066
|
+
font-size: clamp(9px, 2.5vw, 11px) !important;
|
|
4067
|
+
-webkit-line-clamp: 1; /* Single line on very small screens */
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
.uvf-left-side {
|
|
4071
|
+
max-width: 80%;
|
|
4072
|
+
}
|
|
4073
|
+
|
|
3864
4074
|
.uvf-above-seekbar-section {
|
|
3865
4075
|
margin-bottom: 5px;
|
|
3866
4076
|
}
|
|
@@ -4248,6 +4458,47 @@ export class WebPlayer extends BasePlayer {
|
|
|
4248
4458
|
}
|
|
4249
4459
|
|
|
4250
4460
|
|
|
4461
|
+
/* Mobile responsive styles for navigation buttons */
|
|
4462
|
+
@media screen and (max-width: 767px) {
|
|
4463
|
+
.uvf-nav-btn {
|
|
4464
|
+
width: 36px;
|
|
4465
|
+
height: 36px;
|
|
4466
|
+
min-width: 36px;
|
|
4467
|
+
min-height: 36px;
|
|
4468
|
+
}
|
|
4469
|
+
|
|
4470
|
+
.uvf-nav-btn svg {
|
|
4471
|
+
width: 18px;
|
|
4472
|
+
height: 18px;
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4475
|
+
.uvf-navigation-controls {
|
|
4476
|
+
gap: 6px;
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4479
|
+
.uvf-left-side {
|
|
4480
|
+
gap: 8px;
|
|
4481
|
+
max-width: 75%;
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4484
|
+
/* Mobile title adjustments */
|
|
4485
|
+
.uvf-video-title {
|
|
4486
|
+
font-size: clamp(12px, 3vw, 16px) !important;
|
|
4487
|
+
line-height: 1.2;
|
|
4488
|
+
}
|
|
4489
|
+
|
|
4490
|
+
.uvf-video-subtitle {
|
|
4491
|
+
font-size: clamp(10px, 2.2vw, 12px) !important;
|
|
4492
|
+
margin-top: 1px;
|
|
4493
|
+
/* Allow wrapping on mobile if needed */
|
|
4494
|
+
white-space: normal;
|
|
4495
|
+
display: -webkit-box;
|
|
4496
|
+
-webkit-line-clamp: 2;
|
|
4497
|
+
-webkit-box-orient: vertical;
|
|
4498
|
+
overflow: hidden;
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4251
4502
|
/* Mobile portrait - hide skip buttons, ensure top bar visible */
|
|
4252
4503
|
@media screen and (max-width: 767px) and (orientation: portrait) {
|
|
4253
4504
|
#uvf-skip-back,
|
|
@@ -4415,6 +4666,31 @@ export class WebPlayer extends BasePlayer {
|
|
|
4415
4666
|
|
|
4416
4667
|
/* Tablet devices - Enhanced UX with desktop features */
|
|
4417
4668
|
@media screen and (min-width: 768px) and (max-width: 1023px) {
|
|
4669
|
+
/* Tablet navigation and title adjustments */
|
|
4670
|
+
.uvf-nav-btn {
|
|
4671
|
+
width: 38px;
|
|
4672
|
+
height: 38px;
|
|
4673
|
+
min-width: 38px;
|
|
4674
|
+
min-height: 38px;
|
|
4675
|
+
}
|
|
4676
|
+
|
|
4677
|
+
.uvf-nav-btn svg {
|
|
4678
|
+
width: 19px;
|
|
4679
|
+
height: 19px;
|
|
4680
|
+
}
|
|
4681
|
+
|
|
4682
|
+
.uvf-left-side {
|
|
4683
|
+
max-width: 70%;
|
|
4684
|
+
}
|
|
4685
|
+
|
|
4686
|
+
.uvf-video-title {
|
|
4687
|
+
font-size: clamp(15px, 2.2vw, 17px) !important;
|
|
4688
|
+
}
|
|
4689
|
+
|
|
4690
|
+
.uvf-video-subtitle {
|
|
4691
|
+
font-size: clamp(12px, 1.8vw, 13px) !important;
|
|
4692
|
+
}
|
|
4693
|
+
|
|
4418
4694
|
.uvf-controls-bar {
|
|
4419
4695
|
padding: 18px 16px;
|
|
4420
4696
|
background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 70%, var(--uvf-overlay-transparent) 100%);
|
|
@@ -4947,6 +5223,40 @@ export class WebPlayer extends BasePlayer {
|
|
|
4947
5223
|
}
|
|
4948
5224
|
}
|
|
4949
5225
|
|
|
5226
|
+
/* Desktop styles for title and navigation */
|
|
5227
|
+
@media screen and (min-width: 1024px) {
|
|
5228
|
+
.uvf-left-side {
|
|
5229
|
+
max-width: 65%; /* More space for title on desktop */
|
|
5230
|
+
}
|
|
5231
|
+
|
|
5232
|
+
.uvf-video-title {
|
|
5233
|
+
font-size: clamp(16px, 1.8vw, 20px) !important;
|
|
5234
|
+
font-weight: 700; /* Bolder on desktop */
|
|
5235
|
+
}
|
|
5236
|
+
|
|
5237
|
+
.uvf-video-subtitle {
|
|
5238
|
+
font-size: clamp(13px, 1.4vw, 15px) !important;
|
|
5239
|
+
margin-top: 3px;
|
|
5240
|
+
}
|
|
5241
|
+
|
|
5242
|
+
/* Allow hover effects on desktop */
|
|
5243
|
+
.uvf-title-bar:hover .uvf-video-title {
|
|
5244
|
+
color: var(--uvf-accent-1, #ff0000);
|
|
5245
|
+
transition: color 0.3s ease;
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
5248
|
+
|
|
5249
|
+
/* Ultra-wide screens */
|
|
5250
|
+
@media screen and (min-width: 1440px) {
|
|
5251
|
+
.uvf-video-title {
|
|
5252
|
+
font-size: clamp(18px, 1.6vw, 22px) !important;
|
|
5253
|
+
}
|
|
5254
|
+
|
|
5255
|
+
.uvf-video-subtitle {
|
|
5256
|
+
font-size: clamp(14px, 1.2vw, 16px) !important;
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5259
|
+
|
|
4950
5260
|
/* Paywall Desktop */
|
|
4951
5261
|
@media screen and (min-width: 1024px) {
|
|
4952
5262
|
.uvf-paywall-modal {
|
|
@@ -5022,6 +5332,132 @@ export class WebPlayer extends BasePlayer {
|
|
|
5022
5332
|
this.debugLog('Framework branding added');
|
|
5023
5333
|
}
|
|
5024
5334
|
|
|
5335
|
+
/**
|
|
5336
|
+
* Create navigation buttons (back/close) based on configuration
|
|
5337
|
+
*/
|
|
5338
|
+
private createNavigationButtons(container: HTMLElement): void {
|
|
5339
|
+
const navigationConfig = (this.config as any).navigation;
|
|
5340
|
+
if (!navigationConfig) return;
|
|
5341
|
+
|
|
5342
|
+
const { backButton, closeButton } = navigationConfig;
|
|
5343
|
+
|
|
5344
|
+
// Back button
|
|
5345
|
+
if (backButton?.enabled) {
|
|
5346
|
+
const backBtn = document.createElement('button');
|
|
5347
|
+
backBtn.className = 'uvf-control-btn uvf-nav-btn';
|
|
5348
|
+
backBtn.id = 'uvf-back-btn';
|
|
5349
|
+
backBtn.title = backButton.title || 'Back';
|
|
5350
|
+
backBtn.setAttribute('aria-label', backButton.ariaLabel || 'Go back');
|
|
5351
|
+
|
|
5352
|
+
// Get icon based on config
|
|
5353
|
+
const backIcon = this.getNavigationIcon(backButton.icon || 'arrow', backButton.customIcon);
|
|
5354
|
+
backBtn.innerHTML = backIcon;
|
|
5355
|
+
|
|
5356
|
+
// Add click handler
|
|
5357
|
+
backBtn.addEventListener('click', async (e) => {
|
|
5358
|
+
e.preventDefault();
|
|
5359
|
+
e.stopPropagation();
|
|
5360
|
+
|
|
5361
|
+
if (backButton.onClick) {
|
|
5362
|
+
await backButton.onClick();
|
|
5363
|
+
} else if (backButton.href) {
|
|
5364
|
+
if (backButton.replace) {
|
|
5365
|
+
window.history.replaceState(null, '', backButton.href);
|
|
5366
|
+
} else {
|
|
5367
|
+
window.location.href = backButton.href;
|
|
5368
|
+
}
|
|
5369
|
+
} else {
|
|
5370
|
+
// Default: go back in history
|
|
5371
|
+
window.history.back();
|
|
5372
|
+
}
|
|
5373
|
+
|
|
5374
|
+
this.emit('navigationBackClicked');
|
|
5375
|
+
});
|
|
5376
|
+
|
|
5377
|
+
container.appendChild(backBtn);
|
|
5378
|
+
}
|
|
5379
|
+
|
|
5380
|
+
// Close button
|
|
5381
|
+
if (closeButton?.enabled) {
|
|
5382
|
+
const closeBtn = document.createElement('button');
|
|
5383
|
+
closeBtn.className = 'uvf-control-btn uvf-nav-btn';
|
|
5384
|
+
closeBtn.id = 'uvf-close-btn';
|
|
5385
|
+
closeBtn.title = closeButton.title || 'Close';
|
|
5386
|
+
closeBtn.setAttribute('aria-label', closeButton.ariaLabel || 'Close player');
|
|
5387
|
+
|
|
5388
|
+
// Get icon based on config
|
|
5389
|
+
const closeIcon = this.getNavigationIcon(closeButton.icon || 'x', closeButton.customIcon);
|
|
5390
|
+
closeBtn.innerHTML = closeIcon;
|
|
5391
|
+
|
|
5392
|
+
// Add click handler
|
|
5393
|
+
closeBtn.addEventListener('click', async (e) => {
|
|
5394
|
+
e.preventDefault();
|
|
5395
|
+
e.stopPropagation();
|
|
5396
|
+
|
|
5397
|
+
if (closeButton.onClick) {
|
|
5398
|
+
await closeButton.onClick();
|
|
5399
|
+
} else {
|
|
5400
|
+
// Default behaviors
|
|
5401
|
+
if (closeButton.exitFullscreen && this.isFullscreen()) {
|
|
5402
|
+
await this.exitFullscreen();
|
|
5403
|
+
}
|
|
5404
|
+
|
|
5405
|
+
if (closeButton.closeModal) {
|
|
5406
|
+
// Hide player or remove from DOM
|
|
5407
|
+
const playerWrapper = this.container?.querySelector('.uvf-player-wrapper') as HTMLElement;
|
|
5408
|
+
if (playerWrapper) {
|
|
5409
|
+
playerWrapper.style.display = 'none';
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
}
|
|
5413
|
+
|
|
5414
|
+
this.emit('navigationCloseClicked');
|
|
5415
|
+
});
|
|
5416
|
+
|
|
5417
|
+
container.appendChild(closeBtn);
|
|
5418
|
+
}
|
|
5419
|
+
}
|
|
5420
|
+
|
|
5421
|
+
/**
|
|
5422
|
+
* Get navigation icon SVG based on type
|
|
5423
|
+
*/
|
|
5424
|
+
private getNavigationIcon(iconType: string, customIcon?: string): string {
|
|
5425
|
+
if (customIcon) {
|
|
5426
|
+
// If it's a URL, create img tag, otherwise assume it's SVG
|
|
5427
|
+
if (customIcon.startsWith('http') || customIcon.includes('.')) {
|
|
5428
|
+
return `<img src="${customIcon}" alt="" style="width: 20px; height: 20px;" />`;
|
|
5429
|
+
}
|
|
5430
|
+
return customIcon;
|
|
5431
|
+
}
|
|
5432
|
+
|
|
5433
|
+
switch (iconType) {
|
|
5434
|
+
case 'arrow':
|
|
5435
|
+
return `<svg viewBox="0 0 24 24">
|
|
5436
|
+
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.42-1.41L7.83 13H20v-2z" fill="currentColor"/>
|
|
5437
|
+
</svg>`;
|
|
5438
|
+
|
|
5439
|
+
case 'chevron':
|
|
5440
|
+
return `<svg viewBox="0 0 24 24">
|
|
5441
|
+
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" fill="currentColor"/>
|
|
5442
|
+
</svg>`;
|
|
5443
|
+
|
|
5444
|
+
case 'x':
|
|
5445
|
+
return `<svg viewBox="0 0 24 24">
|
|
5446
|
+
<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"/>
|
|
5447
|
+
</svg>`;
|
|
5448
|
+
|
|
5449
|
+
case 'close':
|
|
5450
|
+
return `<svg viewBox="0 0 24 24">
|
|
5451
|
+
<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"/>
|
|
5452
|
+
</svg>`;
|
|
5453
|
+
|
|
5454
|
+
default:
|
|
5455
|
+
return `<svg viewBox="0 0 24 24">
|
|
5456
|
+
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.42-1.41L7.83 13H20v-2z" fill="currentColor"/>
|
|
5457
|
+
</svg>`;
|
|
5458
|
+
}
|
|
5459
|
+
}
|
|
5460
|
+
|
|
5025
5461
|
private createCustomControls(container: HTMLElement): void {
|
|
5026
5462
|
// Add gradients
|
|
5027
5463
|
const topGradient = document.createElement('div');
|
|
@@ -5032,23 +5468,34 @@ export class WebPlayer extends BasePlayer {
|
|
|
5032
5468
|
controlsGradient.className = 'uvf-controls-gradient';
|
|
5033
5469
|
container.appendChild(controlsGradient);
|
|
5034
5470
|
|
|
5035
|
-
// Combined top bar:
|
|
5471
|
+
// Combined top bar: navigation buttons → title → controls
|
|
5036
5472
|
const topBar = document.createElement('div');
|
|
5037
5473
|
topBar.className = 'uvf-top-bar';
|
|
5038
5474
|
|
|
5039
|
-
//
|
|
5475
|
+
// Left side container for navigation + title
|
|
5476
|
+
const leftSide = document.createElement('div');
|
|
5477
|
+
leftSide.className = 'uvf-left-side';
|
|
5478
|
+
|
|
5479
|
+
// Navigation buttons (back/close)
|
|
5480
|
+
const navigationControls = document.createElement('div');
|
|
5481
|
+
navigationControls.className = 'uvf-navigation-controls';
|
|
5482
|
+
this.createNavigationButtons(navigationControls);
|
|
5483
|
+
leftSide.appendChild(navigationControls);
|
|
5484
|
+
|
|
5485
|
+
// Title bar (after navigation buttons)
|
|
5040
5486
|
const titleBar = document.createElement('div');
|
|
5041
5487
|
titleBar.className = 'uvf-title-bar';
|
|
5042
5488
|
titleBar.innerHTML = `
|
|
5043
5489
|
<div class="uvf-title-content">
|
|
5044
|
-
<img class="uvf-video-thumb" id="uvf-video-thumb" alt="thumbnail" style="display:none;" />
|
|
5045
5490
|
<div class="uvf-title-text">
|
|
5046
5491
|
<div class=\"uvf-video-title\" id=\"uvf-video-title\" style=\"display:none;\"></div>
|
|
5047
5492
|
<div class=\"uvf-video-subtitle\" id=\"uvf-video-description\" style=\"display:none;\"></div>
|
|
5048
5493
|
</div>
|
|
5049
5494
|
</div>
|
|
5050
5495
|
`;
|
|
5051
|
-
|
|
5496
|
+
leftSide.appendChild(titleBar);
|
|
5497
|
+
|
|
5498
|
+
topBar.appendChild(leftSide);
|
|
5052
5499
|
|
|
5053
5500
|
// Top controls (right side - Cast and Share buttons)
|
|
5054
5501
|
const topControls = document.createElement('div');
|
|
@@ -5895,11 +6342,13 @@ export class WebPlayer extends BasePlayer {
|
|
|
5895
6342
|
break;
|
|
5896
6343
|
case 'ArrowLeft':
|
|
5897
6344
|
e.preventDefault();
|
|
6345
|
+
e.stopImmediatePropagation(); // Prevent duplicate handler triggers
|
|
5898
6346
|
this.seek(Math.max(0, this.video!.currentTime - 10));
|
|
5899
6347
|
shortcutText = '-10s';
|
|
5900
6348
|
break;
|
|
5901
6349
|
case 'ArrowRight':
|
|
5902
6350
|
e.preventDefault();
|
|
6351
|
+
e.stopImmediatePropagation(); // Prevent duplicate handler triggers
|
|
5903
6352
|
this.seek(Math.min(this.video!.duration, this.video!.currentTime + 10));
|
|
5904
6353
|
shortcutText = '+10s';
|
|
5905
6354
|
break;
|
|
@@ -7080,7 +7529,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
7080
7529
|
autoSkip: this.chapterConfig.userPreferences?.autoSkipIntro || false,
|
|
7081
7530
|
onChapterChange: (chapter: Chapter | null) => {
|
|
7082
7531
|
this.debugLog('Core chapter changed:', chapter?.title || 'none');
|
|
7083
|
-
this.emit('
|
|
7532
|
+
this.emit('onChapterchange', chapter);
|
|
7084
7533
|
},
|
|
7085
7534
|
onSegmentEntered: (segment: ChapterSegment) => {
|
|
7086
7535
|
this.debugLog('Core segment entered:', segment.title);
|
|
@@ -8363,6 +8812,168 @@ export class WebPlayer extends BasePlayer {
|
|
|
8363
8812
|
}
|
|
8364
8813
|
}
|
|
8365
8814
|
|
|
8815
|
+
/**
|
|
8816
|
+
* Check if text is truncated and needs tooltip
|
|
8817
|
+
*/
|
|
8818
|
+
private isTextTruncated(element: HTMLElement): boolean {
|
|
8819
|
+
return element.scrollWidth > element.offsetWidth || element.scrollHeight > element.offsetHeight;
|
|
8820
|
+
}
|
|
8821
|
+
|
|
8822
|
+
/**
|
|
8823
|
+
* Show tooltip for truncated text
|
|
8824
|
+
*/
|
|
8825
|
+
private showTextTooltip(element: HTMLElement, text: string): void {
|
|
8826
|
+
// Remove existing tooltip
|
|
8827
|
+
const existingTooltip = element.querySelector('.uvf-text-tooltip');
|
|
8828
|
+
if (existingTooltip) {
|
|
8829
|
+
existingTooltip.remove();
|
|
8830
|
+
}
|
|
8831
|
+
|
|
8832
|
+
// Create tooltip
|
|
8833
|
+
const tooltip = document.createElement('div');
|
|
8834
|
+
tooltip.className = 'uvf-text-tooltip';
|
|
8835
|
+
tooltip.textContent = text;
|
|
8836
|
+
|
|
8837
|
+
element.appendChild(tooltip);
|
|
8838
|
+
|
|
8839
|
+
// Show tooltip with delay
|
|
8840
|
+
setTimeout(() => {
|
|
8841
|
+
tooltip.classList.add('show');
|
|
8842
|
+
}, 100);
|
|
8843
|
+
}
|
|
8844
|
+
|
|
8845
|
+
/**
|
|
8846
|
+
* Hide tooltip
|
|
8847
|
+
*/
|
|
8848
|
+
private hideTextTooltip(element: HTMLElement): void {
|
|
8849
|
+
const tooltip = element.querySelector('.uvf-text-tooltip');
|
|
8850
|
+
if (tooltip) {
|
|
8851
|
+
tooltip.classList.remove('show');
|
|
8852
|
+
setTimeout(() => {
|
|
8853
|
+
if (tooltip.parentElement) {
|
|
8854
|
+
tooltip.remove();
|
|
8855
|
+
}
|
|
8856
|
+
}, 300);
|
|
8857
|
+
}
|
|
8858
|
+
}
|
|
8859
|
+
|
|
8860
|
+
/**
|
|
8861
|
+
* Setup tooltip handlers for title and description
|
|
8862
|
+
*/
|
|
8863
|
+
private setupTextTooltips(): void {
|
|
8864
|
+
const titleElement = document.getElementById('uvf-video-title');
|
|
8865
|
+
const descElement = document.getElementById('uvf-video-description');
|
|
8866
|
+
|
|
8867
|
+
if (titleElement) {
|
|
8868
|
+
titleElement.addEventListener('mouseenter', () => {
|
|
8869
|
+
const titleText = (this.source?.metadata?.title || '').toString().trim();
|
|
8870
|
+
if (this.isTextTruncated(titleElement) && titleText) {
|
|
8871
|
+
this.showTextTooltip(titleElement, titleText);
|
|
8872
|
+
}
|
|
8873
|
+
});
|
|
8874
|
+
|
|
8875
|
+
titleElement.addEventListener('mouseleave', () => {
|
|
8876
|
+
this.hideTextTooltip(titleElement);
|
|
8877
|
+
});
|
|
8878
|
+
|
|
8879
|
+
// Touch support for mobile
|
|
8880
|
+
titleElement.addEventListener('touchstart', () => {
|
|
8881
|
+
const titleText = (this.source?.metadata?.title || '').toString().trim();
|
|
8882
|
+
if (this.isTextTruncated(titleElement) && titleText) {
|
|
8883
|
+
this.showTextTooltip(titleElement, titleText);
|
|
8884
|
+
// Auto-hide after 3 seconds on touch
|
|
8885
|
+
setTimeout(() => {
|
|
8886
|
+
this.hideTextTooltip(titleElement);
|
|
8887
|
+
}, 3000);
|
|
8888
|
+
}
|
|
8889
|
+
});
|
|
8890
|
+
}
|
|
8891
|
+
|
|
8892
|
+
if (descElement) {
|
|
8893
|
+
descElement.addEventListener('mouseenter', () => {
|
|
8894
|
+
const descText = (this.source?.metadata?.description || '').toString().trim();
|
|
8895
|
+
if (this.isTextTruncated(descElement) && descText) {
|
|
8896
|
+
this.showTextTooltip(descElement, descText);
|
|
8897
|
+
}
|
|
8898
|
+
});
|
|
8899
|
+
|
|
8900
|
+
descElement.addEventListener('mouseleave', () => {
|
|
8901
|
+
this.hideTextTooltip(descElement);
|
|
8902
|
+
});
|
|
8903
|
+
|
|
8904
|
+
// Touch support for mobile
|
|
8905
|
+
descElement.addEventListener('touchstart', () => {
|
|
8906
|
+
const descText = (this.source?.metadata?.description || '').toString().trim();
|
|
8907
|
+
if (this.isTextTruncated(descElement) && descText) {
|
|
8908
|
+
this.showTextTooltip(descElement, descText);
|
|
8909
|
+
// Auto-hide after 3 seconds on touch
|
|
8910
|
+
setTimeout(() => {
|
|
8911
|
+
this.hideTextTooltip(descElement);
|
|
8912
|
+
}, 3000);
|
|
8913
|
+
}
|
|
8914
|
+
});
|
|
8915
|
+
}
|
|
8916
|
+
}
|
|
8917
|
+
|
|
8918
|
+
/**
|
|
8919
|
+
* Smart text truncation based on word count
|
|
8920
|
+
*/
|
|
8921
|
+
private smartTruncateText(text: string, maxWords: number = 12): { truncated: string, needsTooltip: boolean } {
|
|
8922
|
+
const words = text.split(' ');
|
|
8923
|
+
if (words.length <= maxWords) {
|
|
8924
|
+
return { truncated: text, needsTooltip: false };
|
|
8925
|
+
}
|
|
8926
|
+
|
|
8927
|
+
const truncated = words.slice(0, maxWords).join(' ') + '...';
|
|
8928
|
+
return { truncated, needsTooltip: true };
|
|
8929
|
+
}
|
|
8930
|
+
|
|
8931
|
+
/**
|
|
8932
|
+
* Apply smart text display based on screen size and content length
|
|
8933
|
+
*/
|
|
8934
|
+
private applySmartTextDisplay(titleEl: HTMLElement | null, descEl: HTMLElement | null, titleText: string, descText: string): void {
|
|
8935
|
+
const isDesktop = window.innerWidth >= 1024;
|
|
8936
|
+
const isMobile = window.innerWidth < 768;
|
|
8937
|
+
|
|
8938
|
+
if (titleEl && titleText) {
|
|
8939
|
+
const wordCount = titleText.split(' ').length;
|
|
8940
|
+
|
|
8941
|
+
if (isDesktop && wordCount > 8 && wordCount <= 15) {
|
|
8942
|
+
// Use multiline for moderately long titles on desktop
|
|
8943
|
+
titleEl.classList.add('multiline');
|
|
8944
|
+
titleEl.textContent = titleText;
|
|
8945
|
+
} else if (wordCount > 12) {
|
|
8946
|
+
// Smart truncation for very long titles
|
|
8947
|
+
const maxWords = isMobile ? 8 : isDesktop ? 12 : 10;
|
|
8948
|
+
const { truncated } = this.smartTruncateText(titleText, maxWords);
|
|
8949
|
+
titleEl.textContent = truncated;
|
|
8950
|
+
titleEl.classList.remove('multiline');
|
|
8951
|
+
} else {
|
|
8952
|
+
titleEl.textContent = titleText;
|
|
8953
|
+
titleEl.classList.remove('multiline');
|
|
8954
|
+
}
|
|
8955
|
+
}
|
|
8956
|
+
|
|
8957
|
+
if (descEl && descText) {
|
|
8958
|
+
const wordCount = descText.split(' ').length;
|
|
8959
|
+
|
|
8960
|
+
if (isDesktop && wordCount > 15 && wordCount <= 25) {
|
|
8961
|
+
// Use multiline for moderately long descriptions on desktop
|
|
8962
|
+
descEl.classList.add('multiline');
|
|
8963
|
+
descEl.textContent = descText;
|
|
8964
|
+
} else if (wordCount > 20) {
|
|
8965
|
+
// Smart truncation for very long descriptions
|
|
8966
|
+
const maxWords = isMobile ? 12 : isDesktop ? 18 : 15;
|
|
8967
|
+
const { truncated } = this.smartTruncateText(descText, maxWords);
|
|
8968
|
+
descEl.textContent = truncated;
|
|
8969
|
+
descEl.classList.remove('multiline');
|
|
8970
|
+
} else {
|
|
8971
|
+
descEl.textContent = descText;
|
|
8972
|
+
descEl.classList.remove('multiline');
|
|
8973
|
+
}
|
|
8974
|
+
}
|
|
8975
|
+
}
|
|
8976
|
+
|
|
8366
8977
|
private updateMetadataUI(): void {
|
|
8367
8978
|
try {
|
|
8368
8979
|
const md = this.source?.metadata || ({} as any);
|
|
@@ -8375,34 +8986,33 @@ export class WebPlayer extends BasePlayer {
|
|
|
8375
8986
|
const descText = (md.description || '').toString().trim();
|
|
8376
8987
|
const thumbUrl = (md.thumbnailUrl || '').toString().trim();
|
|
8377
8988
|
|
|
8378
|
-
//
|
|
8989
|
+
// Apply smart text display with truncation and multiline support
|
|
8990
|
+
this.applySmartTextDisplay(titleEl, descEl, titleText, descText);
|
|
8991
|
+
|
|
8992
|
+
// Show/hide elements
|
|
8379
8993
|
if (titleEl) {
|
|
8380
|
-
titleEl.textContent = titleText;
|
|
8381
8994
|
titleEl.style.display = titleText ? 'block' : 'none';
|
|
8382
8995
|
}
|
|
8383
|
-
|
|
8384
|
-
// Description
|
|
8385
8996
|
if (descEl) {
|
|
8386
|
-
descEl.textContent = descText;
|
|
8387
8997
|
descEl.style.display = descText ? 'block' : 'none';
|
|
8388
8998
|
}
|
|
8389
8999
|
|
|
8390
|
-
// Thumbnail
|
|
9000
|
+
// Thumbnail (removed from layout but keeping for compatibility)
|
|
8391
9001
|
if (thumbEl) {
|
|
8392
|
-
|
|
8393
|
-
thumbEl.src = thumbUrl;
|
|
8394
|
-
thumbEl.style.display = 'block';
|
|
8395
|
-
} else {
|
|
8396
|
-
thumbEl.removeAttribute('src');
|
|
8397
|
-
thumbEl.style.display = 'none';
|
|
8398
|
-
}
|
|
9002
|
+
thumbEl.style.display = 'none'; // Always hidden in new layout
|
|
8399
9003
|
}
|
|
8400
9004
|
|
|
8401
9005
|
// Hide entire title bar if nothing to show
|
|
8402
|
-
const hasAny = !!(titleText || descText
|
|
9006
|
+
const hasAny = !!(titleText || descText);
|
|
8403
9007
|
if (titleBar) {
|
|
8404
9008
|
titleBar.style.display = hasAny ? '' : 'none';
|
|
8405
9009
|
}
|
|
9010
|
+
|
|
9011
|
+
// Setup tooltips for truncated text
|
|
9012
|
+
setTimeout(() => {
|
|
9013
|
+
this.setupTextTooltips();
|
|
9014
|
+
}, 100); // Small delay to ensure elements are rendered
|
|
9015
|
+
|
|
8406
9016
|
} catch (_) { /* ignore */ }
|
|
8407
9017
|
}
|
|
8408
9018
|
|