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.
@@ -51,9 +51,6 @@ export class WebPlayer extends BasePlayer {
51
51
  private currentPlaybackRate = 1;
52
52
  private isDragging: boolean = false;
53
53
 
54
- // Settings menu state guard to keep controls interactive while open
55
- private isSettingsOpen: boolean = false;
56
-
57
54
  // Settings configuration
58
55
  private settingsConfig = {
59
56
  enabled: true, // Show settings button
@@ -100,20 +97,6 @@ export class WebPlayer extends BasePlayer {
100
97
  // Progress bar tooltip state
101
98
  private showTimeTooltip: boolean = false;
102
99
 
103
- // Material You touch gesture state
104
- private lastTapTime: number = 0;
105
- private tapCount: number = 0;
106
- private tapTimeout: NodeJS.Timeout | null = null;
107
- private longPressTimer: NodeJS.Timeout | null = null;
108
- private longPressActive: boolean = false;
109
- private longPressStartTime: number = 0;
110
- private originalPlaybackRate: number = 1;
111
-
112
- // Material You dynamic theming - matches existing theme
113
- private dominantColor: string = '#ff0000'; // Primary accent from theme
114
- private accentColor: string = '#ff4d4f'; // Secondary accent from theme
115
- private surfaceTint: string = 'rgba(255, 0, 0, 0.08)'; // Surface tint for Material containers
116
-
117
100
  // Autoplay enhancement state
118
101
  private autoplayCapabilities: {
119
102
  canAutoplay: boolean;
@@ -359,35 +342,6 @@ export class WebPlayer extends BasePlayer {
359
342
 
360
343
  // Initialize metadata UI to hidden/empty by default
361
344
  this.updateMetadataUI();
362
-
363
- // Enable Material You mobile layout on mobile portrait devices
364
- this.enableMaterialYouMobileIfNeeded();
365
- }
366
-
367
- /**
368
- * Enable Material You mobile layout if on mobile portrait device
369
- */
370
- private enableMaterialYouMobileIfNeeded(): void {
371
- // Check if mobile and portrait
372
- const isMobile = this.isMobileDevice();
373
- const isPortrait = window.innerHeight > window.innerWidth;
374
-
375
- if (isMobile && isPortrait && this.playerWrapper) {
376
- this.debugLog('Enabling Material You mobile layout');
377
- this.playerWrapper.classList.add('uvf-material-you-mobile');
378
-
379
- // Listen for orientation changes
380
- window.addEventListener('resize', () => {
381
- const isNowPortrait = window.innerHeight > window.innerWidth;
382
- if (this.playerWrapper) {
383
- if (isMobile && isNowPortrait) {
384
- this.playerWrapper.classList.add('uvf-material-you-mobile');
385
- } else {
386
- this.playerWrapper.classList.remove('uvf-material-you-mobile');
387
- }
388
- }
389
- });
390
- }
391
345
  }
392
346
 
393
347
  private setupVideoEventListeners(): void {
@@ -480,9 +434,6 @@ export class WebPlayer extends BasePlayer {
480
434
  // Update time display immediately when metadata loads
481
435
  this.updateTimeDisplay();
482
436
 
483
- // Render chapter markers if enabled
484
- this.renderChapterMarkersOnProgressBar();
485
-
486
437
  this.emit('onLoadedMetadata', {
487
438
  duration: this.video.duration || 0,
488
439
  width: this.video.videoWidth || 0,
@@ -2392,8 +2343,6 @@ export class WebPlayer extends BasePlayer {
2392
2343
  --uvf-scrollbar-thumb-hover-start: rgba(255,0,0,0.5);
2393
2344
  --uvf-scrollbar-thumb-hover-end: rgba(255,0,0,0.6);
2394
2345
  --uvf-firefox-scrollbar-color: rgba(255,255,255,0.25);
2395
- /* Material You surface tint */
2396
- --uvf-surface-tint: rgba(255, 0, 0, 0.08);
2397
2346
  }
2398
2347
 
2399
2348
  /* Player focus styles for better UX */
@@ -4449,15 +4398,24 @@ export class WebPlayer extends BasePlayer {
4449
4398
  }
4450
4399
 
4451
4400
  /* Enhanced Responsive Media Queries with UX Best Practices */
4452
-
4453
- /* Material You Mobile Portrait Layout - 25% Black + 50% Video + 25% Black */
4401
+ /* Mobile devices (portrait) - Material You Design (25-50-25 Layout) */
4454
4402
  @media screen and (max-width: 767px) and (orientation: portrait) {
4455
- /* Enable Material You mode with class flag */
4456
- .uvf-player-wrapper.uvf-material-you-mobile {
4457
- /* Full viewport height layout */
4403
+ .uvf-responsive-container {
4404
+ padding: 0;
4405
+ width: 100vw !important;
4406
+ height: 100vh;
4407
+ height: 100dvh;
4408
+ margin: 0;
4409
+ position: fixed;
4410
+ top: 0;
4411
+ left: 0;
4412
+ overflow: hidden;
4413
+ }
4414
+
4415
+ .uvf-responsive-container .uvf-player-wrapper {
4416
+ width: 100vw !important;
4458
4417
  height: 100vh;
4459
4418
  height: 100dvh;
4460
- width: 100vw;
4461
4419
  position: fixed;
4462
4420
  top: 0;
4463
4421
  left: 0;
@@ -4467,8 +4425,8 @@ export class WebPlayer extends BasePlayer {
4467
4425
  overflow: hidden;
4468
4426
  }
4469
4427
 
4470
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-video-container {
4471
- /* Video occupies middle 50% */
4428
+ /* Video container occupies middle 50% */
4429
+ .uvf-responsive-container .uvf-video-container {
4472
4430
  height: 50vh;
4473
4431
  height: 50dvh;
4474
4432
  width: 100vw;
@@ -4476,22 +4434,20 @@ export class WebPlayer extends BasePlayer {
4476
4434
  margin-top: 25vh;
4477
4435
  margin-top: 25dvh;
4478
4436
  aspect-ratio: unset !important;
4479
- /* Match existing theme background */
4480
4437
  background: radial-gradient(ellipse at center, #1a1a2e 0%, #000 100%);
4481
- /* Material Design elevation */
4482
4438
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4),
4483
4439
  0 4px 16px rgba(0, 0, 0, 0.3),
4484
4440
  0 2px 8px rgba(0, 0, 0, 0.2);
4485
4441
  }
4486
4442
 
4487
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-video {
4443
+ .uvf-video {
4488
4444
  width: 100%;
4489
4445
  height: 100%;
4490
4446
  object-fit: contain;
4491
4447
  }
4492
4448
 
4493
- /* Top black section (25%) - Tap zone overlay */
4494
- .uvf-player-wrapper.uvf-material-you-mobile::before {
4449
+ /* Top black section (25%) - Tap zone */
4450
+ .uvf-player-wrapper::before {
4495
4451
  content: '';
4496
4452
  position: absolute;
4497
4453
  top: 0;
@@ -4505,8 +4461,8 @@ export class WebPlayer extends BasePlayer {
4505
4461
  touch-action: manipulation;
4506
4462
  }
4507
4463
 
4508
- /* Bottom black section (25%) - Surface container */
4509
- .uvf-player-wrapper.uvf-material-you-mobile::after {
4464
+ /* Bottom black section (25%) - Controls area */
4465
+ .uvf-player-wrapper::after {
4510
4466
  content: '';
4511
4467
  position: absolute;
4512
4468
  bottom: 0;
@@ -4523,7 +4479,7 @@ export class WebPlayer extends BasePlayer {
4523
4479
  }
4524
4480
 
4525
4481
  /* Material surface container for controls */
4526
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-controls-bar {
4482
+ .uvf-controls-bar {
4527
4483
  position: absolute;
4528
4484
  bottom: 0;
4529
4485
  left: 0;
@@ -4538,13 +4494,12 @@ export class WebPlayer extends BasePlayer {
4538
4494
  display: flex;
4539
4495
  flex-direction: column;
4540
4496
  justify-content: flex-end;
4541
- /* Material Design surface with tint */
4542
4497
  backdrop-filter: blur(24px);
4543
4498
  -webkit-backdrop-filter: blur(24px);
4544
4499
  }
4545
4500
 
4546
4501
  /* Material surface tint overlay */
4547
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-controls-bar::before {
4502
+ .uvf-controls-bar::before {
4548
4503
  content: '';
4549
4504
  position: absolute;
4550
4505
  inset: 0;
@@ -4555,87 +4510,53 @@ export class WebPlayer extends BasePlayer {
4555
4510
  }
4556
4511
 
4557
4512
  /* Progress bar with chapter markers */
4558
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-section {
4513
+ .uvf-progress-section {
4559
4514
  margin-bottom: 12px;
4560
4515
  position: relative;
4561
4516
  }
4562
4517
 
4563
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-bar-wrapper {
4518
+ .uvf-progress-bar-wrapper {
4564
4519
  padding: 12px 0;
4565
4520
  position: relative;
4566
4521
  }
4567
4522
 
4568
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-bar {
4523
+ .uvf-progress-bar {
4569
4524
  height: 4px;
4570
4525
  background: rgba(255, 255, 255, 0.2);
4571
4526
  border-radius: 4px;
4572
4527
  position: relative;
4573
4528
  overflow: visible;
4574
- /* Material elevation */
4575
4529
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
4576
4530
  }
4577
4531
 
4578
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-filled {
4532
+ .uvf-progress-filled {
4579
4533
  background: var(--uvf-accent-1, #ff0000);
4580
4534
  box-shadow: 0 0 8px var(--uvf-accent-1, #ff0000);
4581
4535
  }
4582
4536
 
4583
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-handle {
4537
+ .uvf-progress-handle {
4584
4538
  width: 20px;
4585
4539
  height: 20px;
4586
4540
  background: var(--uvf-accent-1, #ff0000);
4587
- /* Material Design state layer */
4588
4541
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3),
4589
4542
  0 0 0 0 var(--uvf-accent-1, #ff0000);
4590
4543
  transition: box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
4591
4544
  }
4592
4545
 
4593
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-progress-handle:active {
4546
+ .uvf-progress-handle:active {
4594
4547
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4),
4595
4548
  0 0 0 12px rgba(255, 0, 0, 0.15);
4596
4549
  transform: translate(-50%, -50%) scale(1.2);
4597
4550
  }
4598
4551
 
4599
- /* Chapter markers on progress bar */
4600
- .uvf-chapter-marker {
4601
- position: absolute;
4602
- top: 0;
4603
- height: 100%;
4604
- width: 3px;
4605
- background: rgba(255, 255, 255, 0.4);
4606
- border-radius: 2px;
4607
- transform: translateX(-50%);
4608
- pointer-events: none;
4609
- z-index: 1;
4610
- transition: all 0.2s ease;
4611
- }
4612
-
4613
- .uvf-chapter-marker.intro {
4614
- background: #4CAF50;
4615
- }
4616
-
4617
- .uvf-chapter-marker.recap {
4618
- background: #FFC107;
4619
- }
4620
-
4621
- .uvf-chapter-marker.credits {
4622
- background: #9C27B0;
4623
- }
4624
-
4625
- .uvf-progress-bar-wrapper:hover .uvf-chapter-marker {
4626
- width: 4px;
4627
- opacity: 1;
4628
- }
4629
-
4630
4552
  /* Material Design control buttons */
4631
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn {
4553
+ .uvf-control-btn {
4632
4554
  width: 48px;
4633
4555
  height: 48px;
4634
4556
  min-width: 48px;
4635
4557
  min-height: 48px;
4636
4558
  background: rgba(255, 255, 255, 0.12);
4637
4559
  border-radius: 24px;
4638
- /* Material elevation level 1 */
4639
4560
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12),
4640
4561
  0 1px 2px rgba(0, 0, 0, 0.24);
4641
4562
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
@@ -4644,7 +4565,7 @@ export class WebPlayer extends BasePlayer {
4644
4565
  }
4645
4566
 
4646
4567
  /* Material ripple effect */
4647
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn::before {
4568
+ .uvf-control-btn::before {
4648
4569
  content: '';
4649
4570
  position: absolute;
4650
4571
  inset: 0;
@@ -4654,24 +4575,22 @@ export class WebPlayer extends BasePlayer {
4654
4575
  transition: opacity 0.2s ease;
4655
4576
  }
4656
4577
 
4657
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn:active::before {
4578
+ .uvf-control-btn:active::before {
4658
4579
  opacity: 1;
4659
4580
  }
4660
4581
 
4661
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn:active {
4662
- /* Material elevation level 2 */
4582
+ .uvf-control-btn:active {
4663
4583
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16),
4664
4584
  0 3px 6px rgba(0, 0, 0, 0.23);
4665
4585
  transform: scale(0.95);
4666
4586
  }
4667
4587
 
4668
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn.play-pause {
4588
+ .uvf-control-btn.play-pause {
4669
4589
  width: 56px;
4670
4590
  height: 56px;
4671
4591
  min-width: 56px;
4672
4592
  min-height: 56px;
4673
4593
  border-radius: 28px;
4674
- /* Material elevated button */
4675
4594
  background: linear-gradient(135deg,
4676
4595
  var(--uvf-accent-1, #ff0000),
4677
4596
  var(--uvf-accent-2, #ff4d4f));
@@ -4680,21 +4599,31 @@ export class WebPlayer extends BasePlayer {
4680
4599
  0 0 0 0 var(--uvf-accent-1, #ff0000);
4681
4600
  }
4682
4601
 
4683
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-control-btn.play-pause:active {
4602
+ .uvf-control-btn.play-pause:active {
4684
4603
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.25),
4685
4604
  0 4px 8px rgba(0, 0, 0, 0.20),
4686
4605
  0 0 0 8px rgba(255, 0, 0, 0.12);
4687
4606
  }
4688
4607
 
4608
+ .uvf-control-btn svg {
4609
+ width: 20px;
4610
+ height: 20px;
4611
+ }
4612
+
4613
+ .uvf-control-btn.play-pause svg {
4614
+ width: 24px;
4615
+ height: 24px;
4616
+ }
4617
+
4689
4618
  /* Controls row with Material spacing */
4690
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-controls-row {
4619
+ .uvf-controls-row {
4691
4620
  gap: 16px;
4692
4621
  padding: 0;
4693
4622
  align-items: center;
4694
4623
  }
4695
4624
 
4696
4625
  /* Time display with Material surface */
4697
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-time-display {
4626
+ .uvf-time-display {
4698
4627
  background: rgba(255, 255, 255, 0.1);
4699
4628
  backdrop-filter: blur(8px);
4700
4629
  border-radius: 16px;
@@ -4702,101 +4631,20 @@ export class WebPlayer extends BasePlayer {
4702
4631
  font-size: 13px;
4703
4632
  font-weight: 500;
4704
4633
  font-feature-settings: 'tnum';
4705
- /* Material elevation */
4706
4634
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
4707
4635
  }
4708
4636
 
4709
- /* Double-tap overlay indicators */
4710
- .uvf-doubletap-indicator {
4711
- position: absolute;
4712
- top: 50%;
4713
- transform: translateY(-50%);
4714
- width: 80px;
4715
- height: 80px;
4716
- background: rgba(255, 255, 255, 0.2);
4717
- backdrop-filter: blur(10px);
4718
- border-radius: 40px;
4719
- display: flex;
4720
- align-items: center;
4721
- justify-content: center;
4722
- pointer-events: none;
4723
- opacity: 0;
4724
- z-index: 100;
4725
- transition: opacity 0.3s ease;
4726
- /* Material elevation level 3 */
4727
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19),
4728
- 0 6px 6px rgba(0, 0, 0, 0.23);
4729
- }
4730
-
4731
- .uvf-doubletap-indicator.left {
4732
- left: 15%;
4733
- }
4734
-
4735
- .uvf-doubletap-indicator.right {
4736
- right: 15%;
4737
- }
4738
-
4739
- .uvf-doubletap-indicator.active {
4740
- opacity: 1;
4741
- animation: doubletap-pulse 0.4s cubic-bezier(0.4, 0, 0.2, 1);
4742
- }
4743
-
4744
- @keyframes doubletap-pulse {
4745
- 0% {
4746
- transform: translateY(-50%) scale(0.8);
4747
- opacity: 0;
4748
- }
4749
- 50% {
4750
- transform: translateY(-50%) scale(1.1);
4751
- opacity: 1;
4752
- }
4753
- 100% {
4754
- transform: translateY(-50%) scale(1);
4755
- opacity: 1;
4756
- }
4757
- }
4758
-
4759
- .uvf-doubletap-indicator svg {
4760
- width: 40px;
4761
- height: 40px;
4762
- fill: #fff;
4763
- }
4764
-
4765
- /* Long-press 2x speed indicator */
4766
- .uvf-longpress-indicator {
4767
- position: absolute;
4768
- top: 50%;
4769
- left: 50%;
4770
- transform: translate(-50%, -50%);
4771
- background: rgba(0, 0, 0, 0.8);
4772
- backdrop-filter: blur(16px);
4773
- padding: 16px 24px;
4774
- border-radius: 24px;
4775
- color: #fff;
4776
- font-size: 18px;
4777
- font-weight: 600;
4778
- pointer-events: none;
4779
- opacity: 0;
4780
- z-index: 100;
4781
- transition: opacity 0.2s ease;
4782
- /* Material elevation level 4 */
4783
- box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25),
4784
- 0 10px 10px rgba(0, 0, 0, 0.22);
4785
- }
4786
-
4787
- .uvf-longpress-indicator.active {
4788
- opacity: 1;
4789
- }
4790
-
4791
- /* Hide desktop elements in Material You mode */
4792
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-top-controls,
4793
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-title-bar,
4794
- .uvf-player-wrapper.uvf-material-you-mobile .uvf-volume-control {
4637
+ /* Hide desktop elements */
4638
+ .uvf-top-controls,
4639
+ .uvf-title-bar,
4640
+ .uvf-volume-control,
4641
+ #uvf-skip-back,
4642
+ #uvf-skip-forward {
4795
4643
  display: none !important;
4796
4644
  }
4797
4645
 
4798
4646
  /* Optimize settings button for Material You */
4799
- .uvf-player-wrapper.uvf-material-you-mobile #uvf-settings-btn {
4647
+ #uvf-settings-btn {
4800
4648
  width: 48px !important;
4801
4649
  height: 48px !important;
4802
4650
  min-width: 48px !important;
@@ -4804,405 +4652,6 @@ export class WebPlayer extends BasePlayer {
4804
4652
  border-radius: 24px !important;
4805
4653
  }
4806
4654
  }
4807
-
4808
- /* Mobile devices (portrait) - Enhanced UX with Safe Areas */
4809
- @media screen and (max-width: 767px) and (orientation: portrait) {
4810
- .uvf-responsive-container {
4811
- padding: 0;
4812
- width: 100vw !important;
4813
- height: calc(100vh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
4814
- margin: 0;
4815
- position: relative;
4816
- overflow: hidden;
4817
- }
4818
-
4819
- @supports (height: 100dvh) {
4820
- .uvf-responsive-container {
4821
- height: calc(100dvh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
4822
- }
4823
- }
4824
-
4825
- .uvf-responsive-container .uvf-player-wrapper {
4826
- width: 100vw !important;
4827
- height: 100% !important;
4828
- min-height: calc(100vh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
4829
- }
4830
-
4831
- @supports (height: 100dvh) {
4832
- .uvf-responsive-container .uvf-player-wrapper {
4833
- min-height: calc(100dvh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
4834
- }
4835
- }
4836
-
4837
- .uvf-responsive-container .uvf-video-container {
4838
- width: 100vw !important;
4839
- height: 100% !important;
4840
- aspect-ratio: unset !important;
4841
- min-height: inherit;
4842
- }
4843
-
4844
- /* Enhanced mobile controls bar with safe area padding - iOS Safari specific fixes */
4845
- .uvf-controls-bar {
4846
- position: absolute !important;
4847
- bottom: 0 !important;
4848
- left: 0 !important;
4849
- right: 0 !important;
4850
- padding: 16px 12px;
4851
- padding-bottom: calc(16px + var(--uvf-safe-area-bottom, 0px));
4852
- padding-left: calc(12px + var(--uvf-safe-area-left, 0px));
4853
- padding-right: calc(12px + var(--uvf-safe-area-right, 0px));
4854
- background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 80%, var(--uvf-overlay-transparent) 100%);
4855
- box-sizing: border-box;
4856
- z-index: 1000 !important;
4857
- /* iOS Safari specific fixes */
4858
- transform: translateZ(0);
4859
- -webkit-transform: translateZ(0);
4860
- will-change: transform;
4861
- /* Ensure proper stacking */
4862
- isolation: isolate;
4863
- }
4864
-
4865
- .uvf-progress-section {
4866
- margin-bottom: 16px;
4867
- }
4868
-
4869
- /* Mobile-first responsive controls layout */
4870
- .uvf-controls-row {
4871
- gap: 8px;
4872
- flex-wrap: nowrap;
4873
- align-items: center;
4874
- justify-content: space-between;
4875
- position: relative;
4876
- width: 100%;
4877
- }
4878
-
4879
- /* Left side controls group */
4880
- .uvf-left-controls {
4881
- display: flex;
4882
- align-items: center;
4883
- gap: 8px;
4884
- flex-shrink: 0;
4885
- }
4886
-
4887
- /* Center controls group */
4888
- .uvf-center-controls {
4889
- display: flex;
4890
- align-items: center;
4891
- gap: 8px;
4892
- flex: 1;
4893
- justify-content: center;
4894
- }
4895
-
4896
- /* Mobile control groups reordering */
4897
- .uvf-controls-row .uvf-control-btn.play-pause,
4898
- .uvf-controls-row #uvf-skip-back,
4899
- .uvf-controls-row #uvf-skip-forward {
4900
- order: 1;
4901
- }
4902
-
4903
- .uvf-controls-row .uvf-volume-control {
4904
- order: 2;
4905
- }
4906
-
4907
- .uvf-controls-row .uvf-time-display:not(.uvf-above-seekbar) {
4908
- order: 3;
4909
- margin-left: auto;
4910
- margin-right: 8px;
4911
- }
4912
-
4913
- .uvf-controls-row .uvf-right-controls {
4914
- order: 4;
4915
- margin-left: 0;
4916
- }
4917
-
4918
- /* Touch-friendly control sizing (minimum 44px touch target) */
4919
- .uvf-control-btn {
4920
- width: 44px;
4921
- height: 44px;
4922
- min-width: 44px;
4923
- min-height: 44px;
4924
- border-radius: 22px;
4925
- background: rgba(255,255,255,0.15);
4926
- backdrop-filter: blur(8px);
4927
- }
4928
-
4929
- .uvf-control-btn.play-pause {
4930
- width: 52px;
4931
- height: 52px;
4932
- min-width: 52px;
4933
- min-height: 52px;
4934
- border-radius: 26px;
4935
- background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
4936
- box-shadow: 0 4px 12px rgba(var(--uvf-accent-1), 0.3);
4937
- }
4938
-
4939
- .uvf-control-btn svg {
4940
- width: 20px;
4941
- height: 20px;
4942
- }
4943
-
4944
- .uvf-control-btn.play-pause svg {
4945
- width: 24px;
4946
- height: 24px;
4947
- }
4948
-
4949
- /* Skip buttons with clear visual hierarchy */
4950
- #uvf-skip-back,
4951
- #uvf-skip-forward {
4952
- background: rgba(255,255,255,0.12);
4953
- }
4954
-
4955
- #uvf-skip-back svg,
4956
- #uvf-skip-forward svg {
4957
- width: 22px;
4958
- height: 22px;
4959
- }
4960
-
4961
- /* Mobile time display - compact but readable */
4962
- .uvf-time-display:not(.uvf-above-seekbar) {
4963
- font-size: 12px;
4964
- font-weight: 600;
4965
- padding: 0 6px;
4966
- text-align: center;
4967
- background: rgba(0,0,0,0.3);
4968
- border-radius: 12px;
4969
- text-shadow: 0 1px 3px rgba(0,0,0,0.8);
4970
- flex-shrink: 0;
4971
- }
4972
-
4973
- /* Above-seekbar time display for mobile */
4974
- .uvf-time-display.uvf-above-seekbar {
4975
- font-size: 12px !important;
4976
- font-weight: 500 !important;
4977
- padding: 3px 6px !important;
4978
- background: rgba(0,0,0,0.4) !important;
4979
- border-radius: 10px !important;
4980
- text-shadow: 0 1px 2px rgba(0,0,0,0.8) !important;
4981
- backdrop-filter: blur(4px) !important;
4982
- border: 1px solid rgba(255,255,255,0.1) !important;
4983
- }
4984
-
4985
- /* Simplified volume control for mobile */
4986
- .uvf-volume-control {
4987
- order: 3;
4988
- position: relative;
4989
- }
4990
-
4991
- /* Hide volume panel on mobile - use device controls */
4992
- .uvf-volume-panel {
4993
- display: none;
4994
- }
4995
-
4996
- /* Mobile volume button as simple mute toggle */
4997
- .uvf-volume-control .uvf-control-btn {
4998
- width: 44px;
4999
- height: 44px;
5000
- }
5001
-
5002
- /* Compact right controls for mobile */
5003
- .uvf-right-controls {
5004
- gap: 6px;
5005
- display: flex;
5006
- align-items: center;
5007
- flex-shrink: 0;
5008
- position: relative;
5009
- z-index: 10;
5010
- }
5011
-
5012
- /* Ensure settings container is visible */
5013
- .uvf-right-controls > div[style*="position: relative"],
5014
- .uvf-settings-container {
5015
- display: flex !important;
5016
- position: relative !important;
5017
- align-items: center !important;
5018
- justify-content: center !important;
5019
- min-width: 44px !important;
5020
- min-height: 44px !important;
5021
- }
5022
-
5023
- /* Remove quality badge completely - not essential */
5024
- .uvf-quality-badge {
5025
- display: none !important;
5026
- }
5027
-
5028
- /* Settings menu - hidden by default, accessible via menu */
5029
- .uvf-settings-menu {
5030
- min-width: 160px;
5031
- bottom: 60px;
5032
- right: 12px;
5033
- font-size: 14px;
5034
- max-height: 50vh;
5035
- }
5036
-
5037
- .uvf-settings-option {
5038
- padding: 12px 16px;
5039
- font-size: 14px;
5040
- min-height: 44px;
5041
- display: flex;
5042
- align-items: center;
5043
- }
5044
-
5045
- .uvf-settings-option:hover {
5046
- background: rgba(255,255,255,0.15);
5047
- padding-left: 20px;
5048
- }
5049
-
5050
- /* Simplified settings - hide complex options */
5051
- .uvf-settings-group:first-child .uvf-settings-option[data-speed="0.5"],
5052
- .uvf-settings-group:first-child .uvf-settings-option[data-speed="0.75"],
5053
- .uvf-settings-group:first-child .uvf-settings-option[data-speed="2"] {
5054
- display: none;
5055
- }
5056
- }
5057
-
5058
- /* Enhanced top controls for mobile with safe area support */
5059
- .uvf-top-controls {
5060
- position: absolute;
5061
- top: calc(12px + var(--uvf-safe-area-top));
5062
- right: calc(12px + var(--uvf-safe-area-right));
5063
- display: flex;
5064
- align-items: center;
5065
- gap: 8px;
5066
- z-index: 10;
5067
- }
5068
-
5069
- /* Touch-friendly top buttons */
5070
- .uvf-top-btn {
5071
- width: 44px;
5072
- height: 44px;
5073
- min-width: 44px;
5074
- min-height: 44px;
5075
- background: rgba(0,0,0,0.7);
5076
- backdrop-filter: blur(10px);
5077
- border: 1px solid rgba(255,255,255,0.2);
5078
- }
5079
-
5080
- .uvf-top-btn svg {
5081
- width: 20px;
5082
- height: 20px;
5083
- }
5084
-
5085
- /* Share button - keep visible on all devices */
5086
- #uvf-share-btn {
5087
- display: flex;
5088
- }
5089
-
5090
- /* Enhanced title bar for mobile with safe area support */
5091
- .uvf-title-bar {
5092
- padding: 12px;
5093
- padding-top: calc(12px + var(--uvf-safe-area-top));
5094
- padding-left: calc(12px + var(--uvf-safe-area-left));
5095
- padding-right: calc(12px + var(--uvf-safe-area-right));
5096
- }
5097
-
5098
- .uvf-video-title {
5099
- font-size: 16px;
5100
- font-weight: 700;
5101
- line-height: 1.2;
5102
- }
5103
-
5104
- .uvf-video-subtitle {
5105
- font-size: 12px;
5106
- margin-top: 4px;
5107
- opacity: 0.8;
5108
- }
5109
-
5110
- .uvf-video-thumb {
5111
- width: 48px;
5112
- height: 48px;
5113
- border-radius: 6px;
5114
- }
5115
-
5116
- /* Touch-optimized center play button - uses same themed style as desktop */
5117
- .uvf-center-play-btn {
5118
- width: clamp(72px, 18vw, 96px);
5119
- height: clamp(72px, 18vw, 96px);
5120
- background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
5121
- border: 0;
5122
- box-shadow: 0 10px 30px var(--uvf-accent-1-20);
5123
- }
5124
-
5125
- .uvf-center-play-btn:hover {
5126
- transform: scale(1.06);
5127
- filter: saturate(1.08) brightness(1.05);
5128
- box-shadow: 0 14px 36px var(--uvf-accent-1-20);
5129
- }
5130
-
5131
- .uvf-center-play-btn svg {
5132
- width: clamp(28px, 5.2vw, 38px);
5133
- height: clamp(28px, 5.2vw, 38px);
5134
- margin-left: 4px;
5135
- }
5136
-
5137
- /* Enhanced progress bar for touch */
5138
- .uvf-progress-bar {
5139
- height: 3px;
5140
- margin-bottom: 12px;
5141
- border-radius: 4px;
5142
- background: rgba(255,255,255,0.15);
5143
- position: relative;
5144
- }
5145
-
5146
- /* Larger touch target for progress bar */
5147
- .uvf-progress-bar-wrapper::before {
5148
- content: '';
5149
- position: absolute;
5150
- top: -10px;
5151
- left: 0;
5152
- right: 0;
5153
- bottom: -10px;
5154
- z-index: 1;
5155
- }
5156
-
5157
- .uvf-progress-handle {
5158
- width: 18px;
5159
- height: 18px;
5160
- background: #fff;
5161
- box-shadow: 0 2px 8px rgba(0,0,0,0.3);
5162
- }
5163
-
5164
- .uvf-progress-bar-wrapper:active .uvf-progress-handle {
5165
- transform: translate(-50%, -50%) scale(1.2);
5166
- }
5167
-
5168
- /* Mobile accessibility improvements */
5169
- .uvf-control-btn,
5170
- .uvf-top-btn {
5171
- position: relative;
5172
- overflow: visible;
5173
- }
5174
-
5175
- /* Enhanced focus states for mobile */
5176
- .uvf-control-btn:focus,
5177
- .uvf-top-btn:focus {
5178
- outline: 2px solid var(--uvf-accent-1);
5179
- outline-offset: 2px;
5180
- }
5181
-
5182
-
5183
- /* Show PiP on all devices - modern mobile browsers support it well */
5184
- #uvf-pip-btn {
5185
- display: block;
5186
- background: rgba(255,255,255,0.12);
5187
- border: 1px solid rgba(255,255,255,0.1);
5188
- }
5189
-
5190
- /* Essential controls in right section - Settings, PiP, and Fullscreen only */
5191
- .uvf-right-controls > *:not(#uvf-settings-btn):not(#uvf-fullscreen-btn):not(#uvf-pip-btn) {
5192
- display: none;
5193
- }
5194
-
5195
- /* Ensure settings button is always visible and properly sized on mobile */
5196
- #uvf-settings-btn {
5197
- display: flex !important;
5198
- width: 44px !important;
5199
- height: 44px !important;
5200
- min-width: 44px !important;
5201
- min-height: 44px !important;
5202
- backdrop-filter: blur(8px) !important;
5203
- border-radius: 22px !important;
5204
- align-items: center !important;
5205
- justify-content: center !important;
5206
4655
  }
5207
4656
 
5208
4657
  #uvf-settings-btn svg {
@@ -6130,26 +5579,6 @@ export class WebPlayer extends BasePlayer {
6130
5579
  shortcutIndicator.className = 'uvf-shortcut-indicator';
6131
5580
  shortcutIndicator.id = 'uvf-shortcut-indicator';
6132
5581
  container.appendChild(shortcutIndicator);
6133
-
6134
- // Add Material You double-tap indicators
6135
- const doubleTapLeft = document.createElement('div');
6136
- doubleTapLeft.className = 'uvf-doubletap-indicator left';
6137
- doubleTapLeft.id = 'uvf-doubletap-left';
6138
- 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>';
6139
- container.appendChild(doubleTapLeft);
6140
-
6141
- const doubleTapRight = document.createElement('div');
6142
- doubleTapRight.className = 'uvf-doubletap-indicator right';
6143
- doubleTapRight.id = 'uvf-doubletap-right';
6144
- 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>';
6145
- container.appendChild(doubleTapRight);
6146
-
6147
- // Add Material You long-press indicator
6148
- const longPressIndicator = document.createElement('div');
6149
- longPressIndicator.className = 'uvf-longpress-indicator';
6150
- longPressIndicator.id = 'uvf-longpress-indicator';
6151
- longPressIndicator.textContent = '2x';
6152
- container.appendChild(longPressIndicator);
6153
5582
 
6154
5583
  // Create controls bar
6155
5584
  const controlsBar = document.createElement('div');
@@ -6443,9 +5872,7 @@ export class WebPlayer extends BasePlayer {
6443
5872
  // Play/Pause
6444
5873
  centerPlay?.addEventListener('click', () => this.togglePlayPause());
6445
5874
  playPauseBtn?.addEventListener('click', () => this.togglePlayPause());
6446
-
6447
- // Material You touch gestures for mobile
6448
- this.setupMaterialYouGestures(wrapper);
5875
+ this.video.addEventListener('click', () => this.togglePlayPause());
6449
5876
 
6450
5877
  // Update play/pause icons
6451
5878
  this.video.addEventListener('play', () => {
@@ -6820,12 +6247,7 @@ export class WebPlayer extends BasePlayer {
6820
6247
 
6821
6248
  // Force visibility if menu is active, hide if not active
6822
6249
  if (settingsMenu) {
6823
- const activating = settingsMenu.classList.contains('active');
6824
- if (activating) {
6825
- this.isSettingsOpen = true;
6826
- // Keep controls visible and interactive while menu is open
6827
- this.showControls();
6828
- if (this.hideControlsTimeout) clearTimeout(this.hideControlsTimeout);
6250
+ if (settingsMenu.classList.contains('active')) {
6829
6251
  settingsMenu.style.display = 'block';
6830
6252
  settingsMenu.style.visibility = 'visible';
6831
6253
  settingsMenu.style.opacity = '1';
@@ -6841,13 +6263,10 @@ export class WebPlayer extends BasePlayer {
6841
6263
  settingsMenu.style.padding = '10px 0';
6842
6264
  this.debugLog('Applied fallback styles to show menu');
6843
6265
  } else {
6844
- this.isSettingsOpen = false;
6845
6266
  settingsMenu.style.display = 'none';
6846
6267
  settingsMenu.style.visibility = 'hidden';
6847
6268
  settingsMenu.style.opacity = '0';
6848
6269
  this.debugLog('Applied fallback styles to hide menu');
6849
- // After closing, allow auto-hide again if playing
6850
- this.scheduleHideControls();
6851
6270
  }
6852
6271
  }
6853
6272
 
@@ -6887,19 +6306,6 @@ export class WebPlayer extends BasePlayer {
6887
6306
  }
6888
6307
  });
6889
6308
 
6890
- // Keep controls active while interacting with settings menu
6891
- if (settingsMenu) {
6892
- const keepAlive = () => {
6893
- if (!this.isSettingsOpen) return;
6894
- this.showControls();
6895
- if (this.hideControlsTimeout) clearTimeout(this.hideControlsTimeout);
6896
- };
6897
- settingsMenu.addEventListener('mouseenter', keepAlive);
6898
- settingsMenu.addEventListener('mousemove', keepAlive);
6899
- settingsMenu.addEventListener('touchstart', keepAlive, { passive: true } as any);
6900
- settingsMenu.addEventListener('touchmove', keepAlive, { passive: true } as any);
6901
- }
6902
-
6903
6309
  // Add Escape key handler for settings menu
6904
6310
  document.addEventListener('keydown', (e) => {
6905
6311
  if (e.key === 'Escape' && settingsMenu?.classList.contains('active')) {
@@ -7411,267 +6817,6 @@ export class WebPlayer extends BasePlayer {
7411
6817
  }
7412
6818
  }
7413
6819
 
7414
- /**
7415
- * Setup Material You touch gestures for mobile
7416
- */
7417
- private setupMaterialYouGestures(wrapper: HTMLElement | null): void {
7418
- if (!wrapper || !this.video) return;
7419
-
7420
- const doubleTapLeft = document.getElementById('uvf-doubletap-left');
7421
- const doubleTapRight = document.getElementById('uvf-doubletap-right');
7422
- const longPressIndicator = document.getElementById('uvf-longpress-indicator');
7423
-
7424
- // Single tap, double tap, and long press handling
7425
- const videoElement = this.video;
7426
- const videoContainer = wrapper.querySelector('.uvf-video-container');
7427
-
7428
- if (!videoContainer) return;
7429
-
7430
- // Touch start for long-press detection
7431
- videoContainer.addEventListener('touchstart', (e: Event) => {
7432
- const touchEvent = e as TouchEvent;
7433
- const touch = touchEvent.touches[0];
7434
-
7435
- if (!touch || !videoElement) return;
7436
-
7437
- this.longPressStartTime = Date.now();
7438
-
7439
- // Start long-press timer
7440
- this.longPressTimer = setTimeout(() => {
7441
- this.longPressActive = true;
7442
- this.originalPlaybackRate = videoElement.playbackRate;
7443
- videoElement.playbackRate = 2.0;
7444
-
7445
- // Show indicator
7446
- if (longPressIndicator) {
7447
- longPressIndicator.classList.add('active');
7448
- }
7449
- }, 500); // 500ms for long press
7450
- }, { passive: true });
7451
-
7452
- // Touch end to handle tap/double-tap and stop long-press
7453
- videoContainer.addEventListener('touchend', (e: Event) => {
7454
- const touchEvent = e as TouchEvent;
7455
- const touch = touchEvent.changedTouches[0];
7456
-
7457
- if (!touch || !videoElement) return;
7458
-
7459
- // Clear long-press timer
7460
- if (this.longPressTimer) {
7461
- clearTimeout(this.longPressTimer);
7462
- this.longPressTimer = null;
7463
- }
7464
-
7465
- // If long-press was active, restore playback rate
7466
- if (this.longPressActive) {
7467
- videoElement.playbackRate = this.originalPlaybackRate;
7468
- this.longPressActive = false;
7469
-
7470
- if (longPressIndicator) {
7471
- longPressIndicator.classList.remove('active');
7472
- }
7473
- return; // Don't process as tap
7474
- }
7475
-
7476
- // Tap detection
7477
- const now = Date.now();
7478
- const timeSinceLastTap = now - this.lastTapTime;
7479
-
7480
- if (timeSinceLastTap < 300) { // Double tap detected (within 300ms)
7481
- // Clear single tap timeout
7482
- if (this.tapTimeout) {
7483
- clearTimeout(this.tapTimeout);
7484
- this.tapTimeout = null;
7485
- }
7486
-
7487
- // Determine if left or right side
7488
- const rect = videoContainer.getBoundingClientRect();
7489
- const x = touch.clientX - rect.left;
7490
- const isLeftSide = x < rect.width / 2;
7491
-
7492
- if (isLeftSide) {
7493
- // Skip backward 10s
7494
- this.seek(videoElement.currentTime - 10);
7495
-
7496
- // Show indicator
7497
- if (doubleTapLeft) {
7498
- doubleTapLeft.classList.add('active');
7499
- setTimeout(() => {
7500
- doubleTapLeft.classList.remove('active');
7501
- }, 400);
7502
- }
7503
- } else {
7504
- // Skip forward 10s
7505
- this.seek(videoElement.currentTime + 10);
7506
-
7507
- // Show indicator
7508
- if (doubleTapRight) {
7509
- doubleTapRight.classList.add('active');
7510
- setTimeout(() => {
7511
- doubleTapRight.classList.remove('active');
7512
- }, 400);
7513
- }
7514
- }
7515
-
7516
- this.tapCount = 0;
7517
- this.lastTapTime = 0;
7518
- } else {
7519
- // First tap
7520
- this.tapCount = 1;
7521
- this.lastTapTime = now;
7522
-
7523
- // Wait for potential second tap
7524
- this.tapTimeout = setTimeout(() => {
7525
- if (this.tapCount === 1) {
7526
- // Single tap - toggle overlay controls visibility
7527
- this.toggleControls();
7528
- }
7529
- this.tapCount = 0;
7530
- }, 300); // Wait 300ms for double tap
7531
- }
7532
- }, { passive: true });
7533
-
7534
- // Touch move/cancel to reset long-press
7535
- videoContainer.addEventListener('touchmove', () => {
7536
- if (this.longPressTimer) {
7537
- clearTimeout(this.longPressTimer);
7538
- this.longPressTimer = null;
7539
- }
7540
- }, { passive: true });
7541
-
7542
- videoContainer.addEventListener('touchcancel', () => {
7543
- if (this.longPressTimer) {
7544
- clearTimeout(this.longPressTimer);
7545
- this.longPressTimer = null;
7546
- }
7547
-
7548
- if (this.longPressActive && videoElement) {
7549
- videoElement.playbackRate = this.originalPlaybackRate;
7550
- this.longPressActive = false;
7551
-
7552
- if (longPressIndicator) {
7553
- longPressIndicator.classList.remove('active');
7554
- }
7555
- }
7556
- }, { passive: true });
7557
- }
7558
-
7559
- /**
7560
- * Toggle controls visibility
7561
- */
7562
- private toggleControls(): void {
7563
- const wrapper = this.container?.querySelector('.uvf-player-wrapper');
7564
- if (wrapper) {
7565
- if (wrapper.classList.contains('controls-visible')) {
7566
- this.hideControls();
7567
- } else {
7568
- this.showControls();
7569
- // Auto-hide after delay if playing
7570
- if (this.state.isPlaying) {
7571
- this.scheduleHideControls();
7572
- }
7573
- }
7574
- }
7575
- }
7576
-
7577
- /**
7578
- * Apply dynamic color theming based on video content (Material You)
7579
- * This can be enhanced to extract colors from video frames
7580
- */
7581
- private applyDynamicTheming(primaryColor?: string): void {
7582
- if (!this.playerWrapper) return;
7583
-
7584
- const color = primaryColor || this.dominantColor;
7585
- const rgb = this.hexToRgb(color);
7586
-
7587
- if (rgb) {
7588
- // Update CSS variables for Material You theming
7589
- this.playerWrapper.style.setProperty('--uvf-accent-1', color);
7590
- this.playerWrapper.style.setProperty('--uvf-accent-2', this.lightenColor(color, 10));
7591
- this.playerWrapper.style.setProperty('--uvf-surface-tint', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.08)`);
7592
-
7593
- this.debugLog(`Applied dynamic theming with color: ${color}`);
7594
- }
7595
- }
7596
-
7597
- /**
7598
- * Convert hex color to RGB
7599
- */
7600
- private hexToRgb(hex: string): { r: number, g: number, b: number } | null {
7601
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
7602
- return result ? {
7603
- r: parseInt(result[1], 16),
7604
- g: parseInt(result[2], 16),
7605
- b: parseInt(result[3], 16)
7606
- } : null;
7607
- }
7608
-
7609
- /**
7610
- * Lighten a hex color by a percentage
7611
- */
7612
- private lightenColor(hex: string, percent: number): string {
7613
- const rgb = this.hexToRgb(hex);
7614
- if (!rgb) return hex;
7615
-
7616
- const amount = Math.floor(255 * (percent / 100));
7617
- const r = Math.min(255, rgb.r + amount);
7618
- const g = Math.min(255, rgb.g + amount);
7619
- const b = Math.min(255, rgb.b + amount);
7620
-
7621
- return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
7622
- }
7623
-
7624
- /**
7625
- * Render chapter markers on progress bar for Material You layout
7626
- */
7627
- private renderChapterMarkersOnProgressBar(): void {
7628
- if (!this.video || !this.chapterConfig.enabled || !this.chapterConfig.showChapterMarkers) {
7629
- return;
7630
- }
7631
-
7632
- const progressBar = document.querySelector('.uvf-progress-bar');
7633
- if (!progressBar) return;
7634
-
7635
- // Remove existing markers
7636
- const existingMarkers = progressBar.querySelectorAll('.uvf-chapter-marker');
7637
- existingMarkers.forEach(marker => marker.remove());
7638
-
7639
- // Get chapter data
7640
- const chapters = this.chapterConfig.data;
7641
- if (!chapters || !Array.isArray(chapters) || chapters.length === 0) {
7642
- return;
7643
- }
7644
-
7645
- const duration = this.video.duration;
7646
- if (!duration || duration === 0) return;
7647
-
7648
- // Create marker for each chapter
7649
- chapters.forEach((chapter: any) => {
7650
- if (!chapter.startTime && chapter.startTime !== 0) return;
7651
-
7652
- const marker = document.createElement('div');
7653
- marker.className = 'uvf-chapter-marker';
7654
-
7655
- // Add chapter type class if available
7656
- if (chapter.type) {
7657
- marker.classList.add(chapter.type.toLowerCase());
7658
- }
7659
-
7660
- // Position based on startTime
7661
- const percent = (chapter.startTime / duration) * 100;
7662
- marker.style.left = `${percent}%`;
7663
-
7664
- // Add title attribute for tooltip
7665
- if (chapter.title) {
7666
- marker.setAttribute('title', chapter.title);
7667
- }
7668
-
7669
- progressBar.appendChild(marker);
7670
- });
7671
-
7672
- this.debugLog(`Rendered ${chapters.length} chapter markers on progress bar`);
7673
- }
7674
-
7675
6820
  /**
7676
6821
  * Detect if user is on a mobile device
7677
6822
  */
@@ -7812,8 +6957,6 @@ export class WebPlayer extends BasePlayer {
7812
6957
 
7813
6958
  private hideControls(): void {
7814
6959
  if (!this.state.isPlaying) return;
7815
- // Never hide controls while settings menu is open
7816
- if (this.isSettingsOpen) return;
7817
6960
 
7818
6961
  const wrapper = this.container?.querySelector('.uvf-player-wrapper');
7819
6962
  if (wrapper) {
@@ -7824,14 +6967,12 @@ export class WebPlayer extends BasePlayer {
7824
6967
 
7825
6968
  private scheduleHideControls(): void {
7826
6969
  if (!this.state.isPlaying) return;
7827
- // Do not schedule auto-hide while settings are open
7828
- if (this.isSettingsOpen) return;
7829
6970
 
7830
6971
  if (this.hideControlsTimeout) clearTimeout(this.hideControlsTimeout);
7831
6972
  // Use longer timeout in fullscreen for better UX
7832
6973
  const timeout = this.isFullscreen() ? 4000 : 3000;
7833
6974
  this.hideControlsTimeout = setTimeout(() => {
7834
- if (this.state.isPlaying && !this.controlsContainer?.matches(':hover') && !this.isSettingsOpen) {
6975
+ if (this.state.isPlaying && !this.controlsContainer?.matches(':hover')) {
7835
6976
  this.hideControls();
7836
6977
  }
7837
6978
  }, timeout);
@@ -9040,7 +8181,6 @@ export class WebPlayer extends BasePlayer {
9040
8181
  if (!settingsMenu) return;
9041
8182
 
9042
8183
  settingsMenu.classList.remove('active');
9043
- this.isSettingsOpen = false;
9044
8184
 
9045
8185
  // Apply fallback styles to ensure menu is hidden
9046
8186
  settingsMenu.style.display = 'none';
@@ -9053,8 +8193,6 @@ export class WebPlayer extends BasePlayer {
9053
8193
  });
9054
8194
 
9055
8195
  this.debugLog('Settings menu hidden via hideSettingsMenu()');
9056
- // Resume auto-hide if appropriate
9057
- this.scheduleHideControls();
9058
8196
  }
9059
8197
 
9060
8198
  /**