unified-video-framework 1.4.166 → 1.4.167

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.
@@ -21,7 +21,6 @@ export class WebPlayer extends BasePlayer {
21
21
  this.currentSubtitle = 'off';
22
22
  this.currentPlaybackRate = 1;
23
23
  this.isDragging = false;
24
- this.isSettingsOpen = false;
25
24
  this.settingsConfig = {
26
25
  enabled: true,
27
26
  speed: true,
@@ -53,16 +52,6 @@ export class WebPlayer extends BasePlayer {
53
52
  this.hasTriedButtonFallback = false;
54
53
  this.lastUserInteraction = 0;
55
54
  this.showTimeTooltip = false;
56
- this.lastTapTime = 0;
57
- this.tapCount = 0;
58
- this.tapTimeout = null;
59
- this.longPressTimer = null;
60
- this.longPressActive = false;
61
- this.longPressStartTime = 0;
62
- this.originalPlaybackRate = 1;
63
- this.dominantColor = '#ff0000';
64
- this.accentColor = '#ff4d4f';
65
- this.surfaceTint = 'rgba(255, 0, 0, 0.08)';
66
55
  this.autoplayCapabilities = {
67
56
  canAutoplay: false,
68
57
  canAutoplayMuted: false,
@@ -240,26 +229,6 @@ export class WebPlayer extends BasePlayer {
240
229
  catch (_) { }
241
230
  this.setupCastContextSafe();
242
231
  this.updateMetadataUI();
243
- this.enableMaterialYouMobileIfNeeded();
244
- }
245
- enableMaterialYouMobileIfNeeded() {
246
- const isMobile = this.isMobileDevice();
247
- const isPortrait = window.innerHeight > window.innerWidth;
248
- if (isMobile && isPortrait && this.playerWrapper) {
249
- this.debugLog('Enabling Material You mobile layout');
250
- this.playerWrapper.classList.add('uvf-material-you-mobile');
251
- window.addEventListener('resize', () => {
252
- const isNowPortrait = window.innerHeight > window.innerWidth;
253
- if (this.playerWrapper) {
254
- if (isMobile && isNowPortrait) {
255
- this.playerWrapper.classList.add('uvf-material-you-mobile');
256
- }
257
- else {
258
- this.playerWrapper.classList.remove('uvf-material-you-mobile');
259
- }
260
- }
261
- });
262
- }
263
232
  }
264
233
  setupVideoEventListeners() {
265
234
  if (!this.video)
@@ -337,7 +306,6 @@ export class WebPlayer extends BasePlayer {
337
306
  this.state.duration = this.video.duration || 0;
338
307
  this.debugLog('Metadata loaded - duration:', this.video.duration);
339
308
  this.updateTimeDisplay();
340
- this.renderChapterMarkersOnProgressBar();
341
309
  this.emit('onLoadedMetadata', {
342
310
  duration: this.video.duration || 0,
343
311
  width: this.video.videoWidth || 0,
@@ -1907,8 +1875,6 @@ export class WebPlayer extends BasePlayer {
1907
1875
  --uvf-scrollbar-thumb-hover-start: rgba(255,0,0,0.5);
1908
1876
  --uvf-scrollbar-thumb-hover-end: rgba(255,0,0,0.6);
1909
1877
  --uvf-firefox-scrollbar-color: rgba(255,255,255,0.25);
1910
- /* Material You surface tint */
1911
- --uvf-surface-tint: rgba(255, 0, 0, 0.08);
1912
1878
  }
1913
1879
 
1914
1880
  /* Player focus styles for better UX */
@@ -3964,15 +3930,24 @@ export class WebPlayer extends BasePlayer {
3964
3930
  }
3965
3931
 
3966
3932
  /* Enhanced Responsive Media Queries with UX Best Practices */
3967
-
3968
- /* Material You Mobile Portrait Layout - 25% Black + 50% Video + 25% Black */
3933
+ /* Mobile devices (portrait) - Material You Design (25-50-25 Layout) */
3969
3934
  @media screen and (max-width: 767px) and (orientation: portrait) {
3970
- /* Enable Material You mode with class flag */
3971
- .uvf-player-wrapper.uvf-material-you-mobile {
3972
- /* Full viewport height layout */
3935
+ .uvf-responsive-container {
3936
+ padding: 0;
3937
+ width: 100vw !important;
3938
+ height: 100vh;
3939
+ height: 100dvh;
3940
+ margin: 0;
3941
+ position: fixed;
3942
+ top: 0;
3943
+ left: 0;
3944
+ overflow: hidden;
3945
+ }
3946
+
3947
+ .uvf-responsive-container .uvf-player-wrapper {
3948
+ width: 100vw !important;
3973
3949
  height: 100vh;
3974
3950
  height: 100dvh;
3975
- width: 100vw;
3976
3951
  position: fixed;
3977
3952
  top: 0;
3978
3953
  left: 0;
@@ -3982,8 +3957,8 @@ export class WebPlayer extends BasePlayer {
3982
3957
  overflow: hidden;
3983
3958
  }
3984
3959
 
3985
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-video-container {
3986
- /* Video occupies middle 50% */
3960
+ /* Video container occupies middle 50% */
3961
+ .uvf-responsive-container .uvf-video-container {
3987
3962
  height: 50vh;
3988
3963
  height: 50dvh;
3989
3964
  width: 100vw;
@@ -3991,22 +3966,20 @@ export class WebPlayer extends BasePlayer {
3991
3966
  margin-top: 25vh;
3992
3967
  margin-top: 25dvh;
3993
3968
  aspect-ratio: unset !important;
3994
- /* Match existing theme background */
3995
3969
  background: radial-gradient(ellipse at center, #1a1a2e 0%, #000 100%);
3996
- /* Material Design elevation */
3997
3970
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4),
3998
3971
  0 4px 16px rgba(0, 0, 0, 0.3),
3999
3972
  0 2px 8px rgba(0, 0, 0, 0.2);
4000
3973
  }
4001
3974
 
4002
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-video {
3975
+ .uvf-video {
4003
3976
  width: 100%;
4004
3977
  height: 100%;
4005
3978
  object-fit: contain;
4006
3979
  }
4007
3980
 
4008
- /* Top black section (25%) - Tap zone overlay */
4009
- .uvf-player-wrapper.uvf-material-you-mobile::before {
3981
+ /* Top black section (25%) - Tap zone */
3982
+ .uvf-player-wrapper::before {
4010
3983
  content: '';
4011
3984
  position: absolute;
4012
3985
  top: 0;
@@ -4020,8 +3993,8 @@ export class WebPlayer extends BasePlayer {
4020
3993
  touch-action: manipulation;
4021
3994
  }
4022
3995
 
4023
- /* Bottom black section (25%) - Surface container */
4024
- .uvf-player-wrapper.uvf-material-you-mobile::after {
3996
+ /* Bottom black section (25%) - Controls area */
3997
+ .uvf-player-wrapper::after {
4025
3998
  content: '';
4026
3999
  position: absolute;
4027
4000
  bottom: 0;
@@ -4038,7 +4011,7 @@ export class WebPlayer extends BasePlayer {
4038
4011
  }
4039
4012
 
4040
4013
  /* Material surface container for controls */
4041
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-controls-bar {
4014
+ .uvf-controls-bar {
4042
4015
  position: absolute;
4043
4016
  bottom: 0;
4044
4017
  left: 0;
@@ -4053,13 +4026,12 @@ export class WebPlayer extends BasePlayer {
4053
4026
  display: flex;
4054
4027
  flex-direction: column;
4055
4028
  justify-content: flex-end;
4056
- /* Material Design surface with tint */
4057
4029
  backdrop-filter: blur(24px);
4058
4030
  -webkit-backdrop-filter: blur(24px);
4059
4031
  }
4060
4032
 
4061
4033
  /* Material surface tint overlay */
4062
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-controls-bar::before {
4034
+ .uvf-controls-bar::before {
4063
4035
  content: '';
4064
4036
  position: absolute;
4065
4037
  inset: 0;
@@ -4070,87 +4042,53 @@ export class WebPlayer extends BasePlayer {
4070
4042
  }
4071
4043
 
4072
4044
  /* Progress bar with chapter markers */
4073
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-section {
4045
+ .uvf-progress-section {
4074
4046
  margin-bottom: 12px;
4075
4047
  position: relative;
4076
4048
  }
4077
4049
 
4078
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-bar-wrapper {
4050
+ .uvf-progress-bar-wrapper {
4079
4051
  padding: 12px 0;
4080
4052
  position: relative;
4081
4053
  }
4082
4054
 
4083
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-bar {
4055
+ .uvf-progress-bar {
4084
4056
  height: 4px;
4085
4057
  background: rgba(255, 255, 255, 0.2);
4086
4058
  border-radius: 4px;
4087
4059
  position: relative;
4088
4060
  overflow: visible;
4089
- /* Material elevation */
4090
4061
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
4091
4062
  }
4092
4063
 
4093
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-filled {
4064
+ .uvf-progress-filled {
4094
4065
  background: var(--uvf-accent-1, #ff0000);
4095
4066
  box-shadow: 0 0 8px var(--uvf-accent-1, #ff0000);
4096
4067
  }
4097
4068
 
4098
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-handle {
4069
+ .uvf-progress-handle {
4099
4070
  width: 20px;
4100
4071
  height: 20px;
4101
4072
  background: var(--uvf-accent-1, #ff0000);
4102
- /* Material Design state layer */
4103
4073
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3),
4104
4074
  0 0 0 0 var(--uvf-accent-1, #ff0000);
4105
4075
  transition: box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
4106
4076
  }
4107
4077
 
4108
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-handle:active {
4078
+ .uvf-progress-handle:active {
4109
4079
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4),
4110
4080
  0 0 0 12px rgba(255, 0, 0, 0.15);
4111
4081
  transform: translate(-50%, -50%) scale(1.2);
4112
4082
  }
4113
4083
 
4114
- /* Chapter markers on progress bar */
4115
- .uvf-chapter-marker {
4116
- position: absolute;
4117
- top: 0;
4118
- height: 100%;
4119
- width: 3px;
4120
- background: rgba(255, 255, 255, 0.4);
4121
- border-radius: 2px;
4122
- transform: translateX(-50%);
4123
- pointer-events: none;
4124
- z-index: 1;
4125
- transition: all 0.2s ease;
4126
- }
4127
-
4128
- .uvf-chapter-marker.intro {
4129
- background: #4CAF50;
4130
- }
4131
-
4132
- .uvf-chapter-marker.recap {
4133
- background: #FFC107;
4134
- }
4135
-
4136
- .uvf-chapter-marker.credits {
4137
- background: #9C27B0;
4138
- }
4139
-
4140
- .uvf-progress-bar-wrapper:hover .uvf-chapter-marker {
4141
- width: 4px;
4142
- opacity: 1;
4143
- }
4144
-
4145
4084
  /* Material Design control buttons */
4146
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn {
4085
+ .uvf-control-btn {
4147
4086
  width: 48px;
4148
4087
  height: 48px;
4149
4088
  min-width: 48px;
4150
4089
  min-height: 48px;
4151
4090
  background: rgba(255, 255, 255, 0.12);
4152
4091
  border-radius: 24px;
4153
- /* Material elevation level 1 */
4154
4092
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12),
4155
4093
  0 1px 2px rgba(0, 0, 0, 0.24);
4156
4094
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
@@ -4159,7 +4097,7 @@ export class WebPlayer extends BasePlayer {
4159
4097
  }
4160
4098
 
4161
4099
  /* Material ripple effect */
4162
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn::before {
4100
+ .uvf-control-btn::before {
4163
4101
  content: '';
4164
4102
  position: absolute;
4165
4103
  inset: 0;
@@ -4169,24 +4107,22 @@ export class WebPlayer extends BasePlayer {
4169
4107
  transition: opacity 0.2s ease;
4170
4108
  }
4171
4109
 
4172
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn:active::before {
4110
+ .uvf-control-btn:active::before {
4173
4111
  opacity: 1;
4174
4112
  }
4175
4113
 
4176
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn:active {
4177
- /* Material elevation level 2 */
4114
+ .uvf-control-btn:active {
4178
4115
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16),
4179
4116
  0 3px 6px rgba(0, 0, 0, 0.23);
4180
4117
  transform: scale(0.95);
4181
4118
  }
4182
4119
 
4183
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn.play-pause {
4120
+ .uvf-control-btn.play-pause {
4184
4121
  width: 56px;
4185
4122
  height: 56px;
4186
4123
  min-width: 56px;
4187
4124
  min-height: 56px;
4188
4125
  border-radius: 28px;
4189
- /* Material elevated button */
4190
4126
  background: linear-gradient(135deg,
4191
4127
  var(--uvf-accent-1, #ff0000),
4192
4128
  var(--uvf-accent-2, #ff4d4f));
@@ -4195,21 +4131,31 @@ export class WebPlayer extends BasePlayer {
4195
4131
  0 0 0 0 var(--uvf-accent-1, #ff0000);
4196
4132
  }
4197
4133
 
4198
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn.play-pause:active {
4134
+ .uvf-control-btn.play-pause:active {
4199
4135
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.25),
4200
4136
  0 4px 8px rgba(0, 0, 0, 0.20),
4201
4137
  0 0 0 8px rgba(255, 0, 0, 0.12);
4202
4138
  }
4203
4139
 
4140
+ .uvf-control-btn svg {
4141
+ width: 20px;
4142
+ height: 20px;
4143
+ }
4144
+
4145
+ .uvf-control-btn.play-pause svg {
4146
+ width: 24px;
4147
+ height: 24px;
4148
+ }
4149
+
4204
4150
  /* Controls row with Material spacing */
4205
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-controls-row {
4151
+ .uvf-controls-row {
4206
4152
  gap: 16px;
4207
4153
  padding: 0;
4208
4154
  align-items: center;
4209
4155
  }
4210
4156
 
4211
4157
  /* Time display with Material surface */
4212
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-time-display {
4158
+ .uvf-time-display {
4213
4159
  background: rgba(255, 255, 255, 0.1);
4214
4160
  backdrop-filter: blur(8px);
4215
4161
  border-radius: 16px;
@@ -4217,101 +4163,20 @@ export class WebPlayer extends BasePlayer {
4217
4163
  font-size: 13px;
4218
4164
  font-weight: 500;
4219
4165
  font-feature-settings: 'tnum';
4220
- /* Material elevation */
4221
4166
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
4222
4167
  }
4223
4168
 
4224
- /* Double-tap overlay indicators */
4225
- .uvf-doubletap-indicator {
4226
- position: absolute;
4227
- top: 50%;
4228
- transform: translateY(-50%);
4229
- width: 80px;
4230
- height: 80px;
4231
- background: rgba(255, 255, 255, 0.2);
4232
- backdrop-filter: blur(10px);
4233
- border-radius: 40px;
4234
- display: flex;
4235
- align-items: center;
4236
- justify-content: center;
4237
- pointer-events: none;
4238
- opacity: 0;
4239
- z-index: 100;
4240
- transition: opacity 0.3s ease;
4241
- /* Material elevation level 3 */
4242
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19),
4243
- 0 6px 6px rgba(0, 0, 0, 0.23);
4244
- }
4245
-
4246
- .uvf-doubletap-indicator.left {
4247
- left: 15%;
4248
- }
4249
-
4250
- .uvf-doubletap-indicator.right {
4251
- right: 15%;
4252
- }
4253
-
4254
- .uvf-doubletap-indicator.active {
4255
- opacity: 1;
4256
- animation: doubletap-pulse 0.4s cubic-bezier(0.4, 0, 0.2, 1);
4257
- }
4258
-
4259
- @keyframes doubletap-pulse {
4260
- 0% {
4261
- transform: translateY(-50%) scale(0.8);
4262
- opacity: 0;
4263
- }
4264
- 50% {
4265
- transform: translateY(-50%) scale(1.1);
4266
- opacity: 1;
4267
- }
4268
- 100% {
4269
- transform: translateY(-50%) scale(1);
4270
- opacity: 1;
4271
- }
4272
- }
4273
-
4274
- .uvf-doubletap-indicator svg {
4275
- width: 40px;
4276
- height: 40px;
4277
- fill: #fff;
4278
- }
4279
-
4280
- /* Long-press 2x speed indicator */
4281
- .uvf-longpress-indicator {
4282
- position: absolute;
4283
- top: 50%;
4284
- left: 50%;
4285
- transform: translate(-50%, -50%);
4286
- background: rgba(0, 0, 0, 0.8);
4287
- backdrop-filter: blur(16px);
4288
- padding: 16px 24px;
4289
- border-radius: 24px;
4290
- color: #fff;
4291
- font-size: 18px;
4292
- font-weight: 600;
4293
- pointer-events: none;
4294
- opacity: 0;
4295
- z-index: 100;
4296
- transition: opacity 0.2s ease;
4297
- /* Material elevation level 4 */
4298
- box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25),
4299
- 0 10px 10px rgba(0, 0, 0, 0.22);
4300
- }
4301
-
4302
- .uvf-longpress-indicator.active {
4303
- opacity: 1;
4304
- }
4305
-
4306
- /* Hide desktop elements in Material You mode */
4307
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-top-controls,
4308
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-title-bar,
4309
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-volume-control {
4169
+ /* Hide desktop elements */
4170
+ .uvf-top-controls,
4171
+ .uvf-title-bar,
4172
+ .uvf-volume-control,
4173
+ #uvf-skip-back,
4174
+ #uvf-skip-forward {
4310
4175
  display: none !important;
4311
4176
  }
4312
4177
 
4313
4178
  /* Optimize settings button for Material You */
4314
- .uvf-player-wrapper.uvf-material-you-mobile #uvf-settings-btn {
4179
+ #uvf-settings-btn {
4315
4180
  width: 48px !important;
4316
4181
  height: 48px !important;
4317
4182
  min-width: 48px !important;
@@ -4319,405 +4184,6 @@ export class WebPlayer extends BasePlayer {
4319
4184
  border-radius: 24px !important;
4320
4185
  }
4321
4186
  }
4322
-
4323
- /* Mobile devices (portrait) - Enhanced UX with Safe Areas */
4324
- @media screen and (max-width: 767px) and (orientation: portrait) {
4325
- .uvf-responsive-container {
4326
- padding: 0;
4327
- width: 100vw !important;
4328
- height: calc(100vh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
4329
- margin: 0;
4330
- position: relative;
4331
- overflow: hidden;
4332
- }
4333
-
4334
- @supports (height: 100dvh) {
4335
- .uvf-responsive-container {
4336
- height: calc(100dvh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
4337
- }
4338
- }
4339
-
4340
- .uvf-responsive-container .uvf-player-wrapper {
4341
- width: 100vw !important;
4342
- height: 100% !important;
4343
- min-height: calc(100vh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
4344
- }
4345
-
4346
- @supports (height: 100dvh) {
4347
- .uvf-responsive-container .uvf-player-wrapper {
4348
- min-height: calc(100dvh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
4349
- }
4350
- }
4351
-
4352
- .uvf-responsive-container .uvf-video-container {
4353
- width: 100vw !important;
4354
- height: 100% !important;
4355
- aspect-ratio: unset !important;
4356
- min-height: inherit;
4357
- }
4358
-
4359
- /* Enhanced mobile controls bar with safe area padding - iOS Safari specific fixes */
4360
- .uvf-controls-bar {
4361
- position: absolute !important;
4362
- bottom: 0 !important;
4363
- left: 0 !important;
4364
- right: 0 !important;
4365
- padding: 16px 12px;
4366
- padding-bottom: calc(16px + var(--uvf-safe-area-bottom, 0px));
4367
- padding-left: calc(12px + var(--uvf-safe-area-left, 0px));
4368
- padding-right: calc(12px + var(--uvf-safe-area-right, 0px));
4369
- background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 80%, var(--uvf-overlay-transparent) 100%);
4370
- box-sizing: border-box;
4371
- z-index: 1000 !important;
4372
- /* iOS Safari specific fixes */
4373
- transform: translateZ(0);
4374
- -webkit-transform: translateZ(0);
4375
- will-change: transform;
4376
- /* Ensure proper stacking */
4377
- isolation: isolate;
4378
- }
4379
-
4380
- .uvf-progress-section {
4381
- margin-bottom: 16px;
4382
- }
4383
-
4384
- /* Mobile-first responsive controls layout */
4385
- .uvf-controls-row {
4386
- gap: 8px;
4387
- flex-wrap: nowrap;
4388
- align-items: center;
4389
- justify-content: space-between;
4390
- position: relative;
4391
- width: 100%;
4392
- }
4393
-
4394
- /* Left side controls group */
4395
- .uvf-left-controls {
4396
- display: flex;
4397
- align-items: center;
4398
- gap: 8px;
4399
- flex-shrink: 0;
4400
- }
4401
-
4402
- /* Center controls group */
4403
- .uvf-center-controls {
4404
- display: flex;
4405
- align-items: center;
4406
- gap: 8px;
4407
- flex: 1;
4408
- justify-content: center;
4409
- }
4410
-
4411
- /* Mobile control groups reordering */
4412
- .uvf-controls-row .uvf-control-btn.play-pause,
4413
- .uvf-controls-row #uvf-skip-back,
4414
- .uvf-controls-row #uvf-skip-forward {
4415
- order: 1;
4416
- }
4417
-
4418
- .uvf-controls-row .uvf-volume-control {
4419
- order: 2;
4420
- }
4421
-
4422
- .uvf-controls-row .uvf-time-display:not(.uvf-above-seekbar) {
4423
- order: 3;
4424
- margin-left: auto;
4425
- margin-right: 8px;
4426
- }
4427
-
4428
- .uvf-controls-row .uvf-right-controls {
4429
- order: 4;
4430
- margin-left: 0;
4431
- }
4432
-
4433
- /* Touch-friendly control sizing (minimum 44px touch target) */
4434
- .uvf-control-btn {
4435
- width: 44px;
4436
- height: 44px;
4437
- min-width: 44px;
4438
- min-height: 44px;
4439
- border-radius: 22px;
4440
- background: rgba(255,255,255,0.15);
4441
- backdrop-filter: blur(8px);
4442
- }
4443
-
4444
- .uvf-control-btn.play-pause {
4445
- width: 52px;
4446
- height: 52px;
4447
- min-width: 52px;
4448
- min-height: 52px;
4449
- border-radius: 26px;
4450
- background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
4451
- box-shadow: 0 4px 12px rgba(var(--uvf-accent-1), 0.3);
4452
- }
4453
-
4454
- .uvf-control-btn svg {
4455
- width: 20px;
4456
- height: 20px;
4457
- }
4458
-
4459
- .uvf-control-btn.play-pause svg {
4460
- width: 24px;
4461
- height: 24px;
4462
- }
4463
-
4464
- /* Skip buttons with clear visual hierarchy */
4465
- #uvf-skip-back,
4466
- #uvf-skip-forward {
4467
- background: rgba(255,255,255,0.12);
4468
- }
4469
-
4470
- #uvf-skip-back svg,
4471
- #uvf-skip-forward svg {
4472
- width: 22px;
4473
- height: 22px;
4474
- }
4475
-
4476
- /* Mobile time display - compact but readable */
4477
- .uvf-time-display:not(.uvf-above-seekbar) {
4478
- font-size: 12px;
4479
- font-weight: 600;
4480
- padding: 0 6px;
4481
- text-align: center;
4482
- background: rgba(0,0,0,0.3);
4483
- border-radius: 12px;
4484
- text-shadow: 0 1px 3px rgba(0,0,0,0.8);
4485
- flex-shrink: 0;
4486
- }
4487
-
4488
- /* Above-seekbar time display for mobile */
4489
- .uvf-time-display.uvf-above-seekbar {
4490
- font-size: 12px !important;
4491
- font-weight: 500 !important;
4492
- padding: 3px 6px !important;
4493
- background: rgba(0,0,0,0.4) !important;
4494
- border-radius: 10px !important;
4495
- text-shadow: 0 1px 2px rgba(0,0,0,0.8) !important;
4496
- backdrop-filter: blur(4px) !important;
4497
- border: 1px solid rgba(255,255,255,0.1) !important;
4498
- }
4499
-
4500
- /* Simplified volume control for mobile */
4501
- .uvf-volume-control {
4502
- order: 3;
4503
- position: relative;
4504
- }
4505
-
4506
- /* Hide volume panel on mobile - use device controls */
4507
- .uvf-volume-panel {
4508
- display: none;
4509
- }
4510
-
4511
- /* Mobile volume button as simple mute toggle */
4512
- .uvf-volume-control .uvf-control-btn {
4513
- width: 44px;
4514
- height: 44px;
4515
- }
4516
-
4517
- /* Compact right controls for mobile */
4518
- .uvf-right-controls {
4519
- gap: 6px;
4520
- display: flex;
4521
- align-items: center;
4522
- flex-shrink: 0;
4523
- position: relative;
4524
- z-index: 10;
4525
- }
4526
-
4527
- /* Ensure settings container is visible */
4528
- .uvf-right-controls > div[style*="position: relative"],
4529
- .uvf-settings-container {
4530
- display: flex !important;
4531
- position: relative !important;
4532
- align-items: center !important;
4533
- justify-content: center !important;
4534
- min-width: 44px !important;
4535
- min-height: 44px !important;
4536
- }
4537
-
4538
- /* Remove quality badge completely - not essential */
4539
- .uvf-quality-badge {
4540
- display: none !important;
4541
- }
4542
-
4543
- /* Settings menu - hidden by default, accessible via menu */
4544
- .uvf-settings-menu {
4545
- min-width: 160px;
4546
- bottom: 60px;
4547
- right: 12px;
4548
- font-size: 14px;
4549
- max-height: 50vh;
4550
- }
4551
-
4552
- .uvf-settings-option {
4553
- padding: 12px 16px;
4554
- font-size: 14px;
4555
- min-height: 44px;
4556
- display: flex;
4557
- align-items: center;
4558
- }
4559
-
4560
- .uvf-settings-option:hover {
4561
- background: rgba(255,255,255,0.15);
4562
- padding-left: 20px;
4563
- }
4564
-
4565
- /* Simplified settings - hide complex options */
4566
- .uvf-settings-group:first-child .uvf-settings-option[data-speed="0.5"],
4567
- .uvf-settings-group:first-child .uvf-settings-option[data-speed="0.75"],
4568
- .uvf-settings-group:first-child .uvf-settings-option[data-speed="2"] {
4569
- display: none;
4570
- }
4571
- }
4572
-
4573
- /* Enhanced top controls for mobile with safe area support */
4574
- .uvf-top-controls {
4575
- position: absolute;
4576
- top: calc(12px + var(--uvf-safe-area-top));
4577
- right: calc(12px + var(--uvf-safe-area-right));
4578
- display: flex;
4579
- align-items: center;
4580
- gap: 8px;
4581
- z-index: 10;
4582
- }
4583
-
4584
- /* Touch-friendly top buttons */
4585
- .uvf-top-btn {
4586
- width: 44px;
4587
- height: 44px;
4588
- min-width: 44px;
4589
- min-height: 44px;
4590
- background: rgba(0,0,0,0.7);
4591
- backdrop-filter: blur(10px);
4592
- border: 1px solid rgba(255,255,255,0.2);
4593
- }
4594
-
4595
- .uvf-top-btn svg {
4596
- width: 20px;
4597
- height: 20px;
4598
- }
4599
-
4600
- /* Share button - keep visible on all devices */
4601
- #uvf-share-btn {
4602
- display: flex;
4603
- }
4604
-
4605
- /* Enhanced title bar for mobile with safe area support */
4606
- .uvf-title-bar {
4607
- padding: 12px;
4608
- padding-top: calc(12px + var(--uvf-safe-area-top));
4609
- padding-left: calc(12px + var(--uvf-safe-area-left));
4610
- padding-right: calc(12px + var(--uvf-safe-area-right));
4611
- }
4612
-
4613
- .uvf-video-title {
4614
- font-size: 16px;
4615
- font-weight: 700;
4616
- line-height: 1.2;
4617
- }
4618
-
4619
- .uvf-video-subtitle {
4620
- font-size: 12px;
4621
- margin-top: 4px;
4622
- opacity: 0.8;
4623
- }
4624
-
4625
- .uvf-video-thumb {
4626
- width: 48px;
4627
- height: 48px;
4628
- border-radius: 6px;
4629
- }
4630
-
4631
- /* Touch-optimized center play button - uses same themed style as desktop */
4632
- .uvf-center-play-btn {
4633
- width: clamp(72px, 18vw, 96px);
4634
- height: clamp(72px, 18vw, 96px);
4635
- background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
4636
- border: 0;
4637
- box-shadow: 0 10px 30px var(--uvf-accent-1-20);
4638
- }
4639
-
4640
- .uvf-center-play-btn:hover {
4641
- transform: scale(1.06);
4642
- filter: saturate(1.08) brightness(1.05);
4643
- box-shadow: 0 14px 36px var(--uvf-accent-1-20);
4644
- }
4645
-
4646
- .uvf-center-play-btn svg {
4647
- width: clamp(28px, 5.2vw, 38px);
4648
- height: clamp(28px, 5.2vw, 38px);
4649
- margin-left: 4px;
4650
- }
4651
-
4652
- /* Enhanced progress bar for touch */
4653
- .uvf-progress-bar {
4654
- height: 3px;
4655
- margin-bottom: 12px;
4656
- border-radius: 4px;
4657
- background: rgba(255,255,255,0.15);
4658
- position: relative;
4659
- }
4660
-
4661
- /* Larger touch target for progress bar */
4662
- .uvf-progress-bar-wrapper::before {
4663
- content: '';
4664
- position: absolute;
4665
- top: -10px;
4666
- left: 0;
4667
- right: 0;
4668
- bottom: -10px;
4669
- z-index: 1;
4670
- }
4671
-
4672
- .uvf-progress-handle {
4673
- width: 18px;
4674
- height: 18px;
4675
- background: #fff;
4676
- box-shadow: 0 2px 8px rgba(0,0,0,0.3);
4677
- }
4678
-
4679
- .uvf-progress-bar-wrapper:active .uvf-progress-handle {
4680
- transform: translate(-50%, -50%) scale(1.2);
4681
- }
4682
-
4683
- /* Mobile accessibility improvements */
4684
- .uvf-control-btn,
4685
- .uvf-top-btn {
4686
- position: relative;
4687
- overflow: visible;
4688
- }
4689
-
4690
- /* Enhanced focus states for mobile */
4691
- .uvf-control-btn:focus,
4692
- .uvf-top-btn:focus {
4693
- outline: 2px solid var(--uvf-accent-1);
4694
- outline-offset: 2px;
4695
- }
4696
-
4697
-
4698
- /* Show PiP on all devices - modern mobile browsers support it well */
4699
- #uvf-pip-btn {
4700
- display: block;
4701
- background: rgba(255,255,255,0.12);
4702
- border: 1px solid rgba(255,255,255,0.1);
4703
- }
4704
-
4705
- /* Essential controls in right section - Settings, PiP, and Fullscreen only */
4706
- .uvf-right-controls > *:not(#uvf-settings-btn):not(#uvf-fullscreen-btn):not(#uvf-pip-btn) {
4707
- display: none;
4708
- }
4709
-
4710
- /* Ensure settings button is always visible and properly sized on mobile */
4711
- #uvf-settings-btn {
4712
- display: flex !important;
4713
- width: 44px !important;
4714
- height: 44px !important;
4715
- min-width: 44px !important;
4716
- min-height: 44px !important;
4717
- backdrop-filter: blur(8px) !important;
4718
- border-radius: 22px !important;
4719
- align-items: center !important;
4720
- justify-content: center !important;
4721
4187
  }
4722
4188
 
4723
4189
  #uvf-settings-btn svg {
@@ -5614,21 +5080,6 @@ export class WebPlayer extends BasePlayer {
5614
5080
  shortcutIndicator.className = 'uvf-shortcut-indicator';
5615
5081
  shortcutIndicator.id = 'uvf-shortcut-indicator';
5616
5082
  container.appendChild(shortcutIndicator);
5617
- const doubleTapLeft = document.createElement('div');
5618
- doubleTapLeft.className = 'uvf-doubletap-indicator left';
5619
- doubleTapLeft.id = 'uvf-doubletap-left';
5620
- doubleTapLeft.innerHTML = '<svg viewBox="0 0 24 24"><path d="M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z"/></svg><div style="margin-top:4px;font-size:14px;font-weight:600;">-10s</div>';
5621
- container.appendChild(doubleTapLeft);
5622
- const doubleTapRight = document.createElement('div');
5623
- doubleTapRight.className = 'uvf-doubletap-indicator right';
5624
- doubleTapRight.id = 'uvf-doubletap-right';
5625
- doubleTapRight.innerHTML = '<svg viewBox="0 0 24 24"><path d="M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z"/></svg><div style="margin-top:4px;font-size:14px;font-weight:600;">+10s</div>';
5626
- container.appendChild(doubleTapRight);
5627
- const longPressIndicator = document.createElement('div');
5628
- longPressIndicator.className = 'uvf-longpress-indicator';
5629
- longPressIndicator.id = 'uvf-longpress-indicator';
5630
- longPressIndicator.textContent = '2x';
5631
- container.appendChild(longPressIndicator);
5632
5083
  const controlsBar = document.createElement('div');
5633
5084
  controlsBar.className = 'uvf-controls-bar';
5634
5085
  controlsBar.id = 'uvf-controls';
@@ -5861,7 +5312,7 @@ export class WebPlayer extends BasePlayer {
5861
5312
  });
5862
5313
  centerPlay?.addEventListener('click', () => this.togglePlayPause());
5863
5314
  playPauseBtn?.addEventListener('click', () => this.togglePlayPause());
5864
- this.setupMaterialYouGestures(wrapper);
5315
+ this.video.addEventListener('click', () => this.togglePlayPause());
5865
5316
  this.video.addEventListener('play', () => {
5866
5317
  const playIcon = document.getElementById('uvf-play-icon');
5867
5318
  const pauseIcon = document.getElementById('uvf-pause-icon');
@@ -6154,12 +5605,7 @@ export class WebPlayer extends BasePlayer {
6154
5605
  this.debugLog('Settings menu classes before toggle:', Array.from(settingsMenu?.classList || []).join(' '));
6155
5606
  settingsMenu?.classList.toggle('active');
6156
5607
  if (settingsMenu) {
6157
- const activating = settingsMenu.classList.contains('active');
6158
- if (activating) {
6159
- this.isSettingsOpen = true;
6160
- this.showControls();
6161
- if (this.hideControlsTimeout)
6162
- clearTimeout(this.hideControlsTimeout);
5608
+ if (settingsMenu.classList.contains('active')) {
6163
5609
  settingsMenu.style.display = 'block';
6164
5610
  settingsMenu.style.visibility = 'visible';
6165
5611
  settingsMenu.style.opacity = '1';
@@ -6176,12 +5622,10 @@ export class WebPlayer extends BasePlayer {
6176
5622
  this.debugLog('Applied fallback styles to show menu');
6177
5623
  }
6178
5624
  else {
6179
- this.isSettingsOpen = false;
6180
5625
  settingsMenu.style.display = 'none';
6181
5626
  settingsMenu.style.visibility = 'hidden';
6182
5627
  settingsMenu.style.opacity = '0';
6183
5628
  this.debugLog('Applied fallback styles to hide menu');
6184
- this.scheduleHideControls();
6185
5629
  }
6186
5630
  }
6187
5631
  this.debugLog('Settings menu classes after toggle:', Array.from(settingsMenu?.classList || []).join(' '));
@@ -6209,19 +5653,6 @@ export class WebPlayer extends BasePlayer {
6209
5653
  this.hideSettingsMenu();
6210
5654
  }
6211
5655
  });
6212
- if (settingsMenu) {
6213
- const keepAlive = () => {
6214
- if (!this.isSettingsOpen)
6215
- return;
6216
- this.showControls();
6217
- if (this.hideControlsTimeout)
6218
- clearTimeout(this.hideControlsTimeout);
6219
- };
6220
- settingsMenu.addEventListener('mouseenter', keepAlive);
6221
- settingsMenu.addEventListener('mousemove', keepAlive);
6222
- settingsMenu.addEventListener('touchstart', keepAlive, { passive: true });
6223
- settingsMenu.addEventListener('touchmove', keepAlive, { passive: true });
6224
- }
6225
5656
  document.addEventListener('keydown', (e) => {
6226
5657
  if (e.key === 'Escape' && settingsMenu?.classList.contains('active')) {
6227
5658
  this.hideSettingsMenu();
@@ -6662,187 +6093,6 @@ export class WebPlayer extends BasePlayer {
6662
6093
  this.mute();
6663
6094
  }
6664
6095
  }
6665
- setupMaterialYouGestures(wrapper) {
6666
- if (!wrapper || !this.video)
6667
- return;
6668
- const doubleTapLeft = document.getElementById('uvf-doubletap-left');
6669
- const doubleTapRight = document.getElementById('uvf-doubletap-right');
6670
- const longPressIndicator = document.getElementById('uvf-longpress-indicator');
6671
- const videoElement = this.video;
6672
- const videoContainer = wrapper.querySelector('.uvf-video-container');
6673
- if (!videoContainer)
6674
- return;
6675
- videoContainer.addEventListener('touchstart', (e) => {
6676
- const touchEvent = e;
6677
- const touch = touchEvent.touches[0];
6678
- if (!touch || !videoElement)
6679
- return;
6680
- this.longPressStartTime = Date.now();
6681
- this.longPressTimer = setTimeout(() => {
6682
- this.longPressActive = true;
6683
- this.originalPlaybackRate = videoElement.playbackRate;
6684
- videoElement.playbackRate = 2.0;
6685
- if (longPressIndicator) {
6686
- longPressIndicator.classList.add('active');
6687
- }
6688
- }, 500);
6689
- }, { passive: true });
6690
- videoContainer.addEventListener('touchend', (e) => {
6691
- const touchEvent = e;
6692
- const touch = touchEvent.changedTouches[0];
6693
- if (!touch || !videoElement)
6694
- return;
6695
- if (this.longPressTimer) {
6696
- clearTimeout(this.longPressTimer);
6697
- this.longPressTimer = null;
6698
- }
6699
- if (this.longPressActive) {
6700
- videoElement.playbackRate = this.originalPlaybackRate;
6701
- this.longPressActive = false;
6702
- if (longPressIndicator) {
6703
- longPressIndicator.classList.remove('active');
6704
- }
6705
- return;
6706
- }
6707
- const now = Date.now();
6708
- const timeSinceLastTap = now - this.lastTapTime;
6709
- if (timeSinceLastTap < 300) {
6710
- if (this.tapTimeout) {
6711
- clearTimeout(this.tapTimeout);
6712
- this.tapTimeout = null;
6713
- }
6714
- const rect = videoContainer.getBoundingClientRect();
6715
- const x = touch.clientX - rect.left;
6716
- const isLeftSide = x < rect.width / 2;
6717
- if (isLeftSide) {
6718
- this.seek(videoElement.currentTime - 10);
6719
- if (doubleTapLeft) {
6720
- doubleTapLeft.classList.add('active');
6721
- setTimeout(() => {
6722
- doubleTapLeft.classList.remove('active');
6723
- }, 400);
6724
- }
6725
- }
6726
- else {
6727
- this.seek(videoElement.currentTime + 10);
6728
- if (doubleTapRight) {
6729
- doubleTapRight.classList.add('active');
6730
- setTimeout(() => {
6731
- doubleTapRight.classList.remove('active');
6732
- }, 400);
6733
- }
6734
- }
6735
- this.tapCount = 0;
6736
- this.lastTapTime = 0;
6737
- }
6738
- else {
6739
- this.tapCount = 1;
6740
- this.lastTapTime = now;
6741
- this.tapTimeout = setTimeout(() => {
6742
- if (this.tapCount === 1) {
6743
- this.toggleControls();
6744
- }
6745
- this.tapCount = 0;
6746
- }, 300);
6747
- }
6748
- }, { passive: true });
6749
- videoContainer.addEventListener('touchmove', () => {
6750
- if (this.longPressTimer) {
6751
- clearTimeout(this.longPressTimer);
6752
- this.longPressTimer = null;
6753
- }
6754
- }, { passive: true });
6755
- videoContainer.addEventListener('touchcancel', () => {
6756
- if (this.longPressTimer) {
6757
- clearTimeout(this.longPressTimer);
6758
- this.longPressTimer = null;
6759
- }
6760
- if (this.longPressActive && videoElement) {
6761
- videoElement.playbackRate = this.originalPlaybackRate;
6762
- this.longPressActive = false;
6763
- if (longPressIndicator) {
6764
- longPressIndicator.classList.remove('active');
6765
- }
6766
- }
6767
- }, { passive: true });
6768
- }
6769
- toggleControls() {
6770
- const wrapper = this.container?.querySelector('.uvf-player-wrapper');
6771
- if (wrapper) {
6772
- if (wrapper.classList.contains('controls-visible')) {
6773
- this.hideControls();
6774
- }
6775
- else {
6776
- this.showControls();
6777
- if (this.state.isPlaying) {
6778
- this.scheduleHideControls();
6779
- }
6780
- }
6781
- }
6782
- }
6783
- applyDynamicTheming(primaryColor) {
6784
- if (!this.playerWrapper)
6785
- return;
6786
- const color = primaryColor || this.dominantColor;
6787
- const rgb = this.hexToRgb(color);
6788
- if (rgb) {
6789
- this.playerWrapper.style.setProperty('--uvf-accent-1', color);
6790
- this.playerWrapper.style.setProperty('--uvf-accent-2', this.lightenColor(color, 10));
6791
- this.playerWrapper.style.setProperty('--uvf-surface-tint', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.08)`);
6792
- this.debugLog(`Applied dynamic theming with color: ${color}`);
6793
- }
6794
- }
6795
- hexToRgb(hex) {
6796
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
6797
- return result ? {
6798
- r: parseInt(result[1], 16),
6799
- g: parseInt(result[2], 16),
6800
- b: parseInt(result[3], 16)
6801
- } : null;
6802
- }
6803
- lightenColor(hex, percent) {
6804
- const rgb = this.hexToRgb(hex);
6805
- if (!rgb)
6806
- return hex;
6807
- const amount = Math.floor(255 * (percent / 100));
6808
- const r = Math.min(255, rgb.r + amount);
6809
- const g = Math.min(255, rgb.g + amount);
6810
- const b = Math.min(255, rgb.b + amount);
6811
- return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
6812
- }
6813
- renderChapterMarkersOnProgressBar() {
6814
- if (!this.video || !this.chapterConfig.enabled || !this.chapterConfig.showChapterMarkers) {
6815
- return;
6816
- }
6817
- const progressBar = document.querySelector('.uvf-progress-bar');
6818
- if (!progressBar)
6819
- return;
6820
- const existingMarkers = progressBar.querySelectorAll('.uvf-chapter-marker');
6821
- existingMarkers.forEach(marker => marker.remove());
6822
- const chapters = this.chapterConfig.data;
6823
- if (!chapters || !Array.isArray(chapters) || chapters.length === 0) {
6824
- return;
6825
- }
6826
- const duration = this.video.duration;
6827
- if (!duration || duration === 0)
6828
- return;
6829
- chapters.forEach((chapter) => {
6830
- if (!chapter.startTime && chapter.startTime !== 0)
6831
- return;
6832
- const marker = document.createElement('div');
6833
- marker.className = 'uvf-chapter-marker';
6834
- if (chapter.type) {
6835
- marker.classList.add(chapter.type.toLowerCase());
6836
- }
6837
- const percent = (chapter.startTime / duration) * 100;
6838
- marker.style.left = `${percent}%`;
6839
- if (chapter.title) {
6840
- marker.setAttribute('title', chapter.title);
6841
- }
6842
- progressBar.appendChild(marker);
6843
- });
6844
- this.debugLog(`Rendered ${chapters.length} chapter markers on progress bar`);
6845
- }
6846
6096
  isMobileDevice() {
6847
6097
  const userAgent = navigator.userAgent.toLowerCase();
6848
6098
  const mobileKeywords = ['android', 'iphone', 'ipad', 'ipod', 'blackberry', 'windows phone', 'mobile'];
@@ -6956,8 +6206,6 @@ export class WebPlayer extends BasePlayer {
6956
6206
  hideControls() {
6957
6207
  if (!this.state.isPlaying)
6958
6208
  return;
6959
- if (this.isSettingsOpen)
6960
- return;
6961
6209
  const wrapper = this.container?.querySelector('.uvf-player-wrapper');
6962
6210
  if (wrapper) {
6963
6211
  wrapper.classList.remove('controls-visible');
@@ -6967,13 +6215,11 @@ export class WebPlayer extends BasePlayer {
6967
6215
  scheduleHideControls() {
6968
6216
  if (!this.state.isPlaying)
6969
6217
  return;
6970
- if (this.isSettingsOpen)
6971
- return;
6972
6218
  if (this.hideControlsTimeout)
6973
6219
  clearTimeout(this.hideControlsTimeout);
6974
6220
  const timeout = this.isFullscreen() ? 4000 : 3000;
6975
6221
  this.hideControlsTimeout = setTimeout(() => {
6976
- if (this.state.isPlaying && !this.controlsContainer?.matches(':hover') && !this.isSettingsOpen) {
6222
+ if (this.state.isPlaying && !this.controlsContainer?.matches(':hover')) {
6977
6223
  this.hideControls();
6978
6224
  }
6979
6225
  }, timeout);
@@ -8010,7 +7256,6 @@ export class WebPlayer extends BasePlayer {
8010
7256
  if (!settingsMenu)
8011
7257
  return;
8012
7258
  settingsMenu.classList.remove('active');
8013
- this.isSettingsOpen = false;
8014
7259
  settingsMenu.style.display = 'none';
8015
7260
  settingsMenu.style.visibility = 'hidden';
8016
7261
  settingsMenu.style.opacity = '0';
@@ -8018,7 +7263,6 @@ export class WebPlayer extends BasePlayer {
8018
7263
  item.classList.remove('expanded');
8019
7264
  });
8020
7265
  this.debugLog('Settings menu hidden via hideSettingsMenu()');
8021
- this.scheduleHideControls();
8022
7266
  }
8023
7267
  updateAccordionAfterSelection(section) {
8024
7268
  setTimeout(() => {