jorisvaneyghen commited on
Commit
004e9ac
·
1 Parent(s): 30f8f43

Fix menu issues

Browse files
Files changed (3) hide show
  1. css/styles.css +176 -47
  2. index.html +114 -160
  3. sw.js +1 -1
css/styles.css CHANGED
@@ -1,13 +1,13 @@
1
  /* Chrome, Safari, Edge, Opera */
2
  input::-webkit-outer-spin-button,
3
  input::-webkit-inner-spin-button {
4
- -webkit-appearance: none;
5
- margin: 0;
6
  }
7
 
8
  /* Firefox */
9
  input[type=number] {
10
- -moz-appearance: textfield;
11
  }
12
 
13
  * {
@@ -402,7 +402,7 @@ input[type="checkbox"] {
402
  display: flex;
403
  flex-direction: column; /* <-- stack rows vertically */
404
  align-items: flex-start;
405
- gap: 12px; /* spacing between rows */
406
  width: 100%;
407
  }
408
 
@@ -481,8 +481,6 @@ input[type="checkbox"] {
481
  }
482
 
483
 
484
-
485
-
486
  /* Popup Styles */
487
  .popup-overlay {
488
  position: fixed;
@@ -498,7 +496,7 @@ input[type="checkbox"] {
498
  backdrop-filter: blur(5px);
499
  }
500
 
501
- .popup-content {
502
  background: linear-gradient(135deg, #1a2a6c, #b21f1f);
503
  border-radius: 15px;
504
  padding: 0;
@@ -522,7 +520,7 @@ input[type="checkbox"] {
522
  }
523
  }
524
 
525
- .popup-header {
526
  display: flex;
527
  justify-content: space-between;
528
  align-items: center;
@@ -532,7 +530,7 @@ input[type="checkbox"] {
532
  }
533
 
534
 
535
- .popup-header h2 {
536
  margin: 0;
537
  font-size: 1.5rem;
538
  }
@@ -556,23 +554,23 @@ input[type="checkbox"] {
556
  background: rgba(255, 255, 255, 0.1);
557
  }
558
 
559
- .popup-body {
560
  padding: 25px;
561
  overflow-y: auto;
562
  max-height: calc(80vh - 80px);
563
  }
564
 
565
- .popup-body p {
566
  margin-bottom: 15px;
567
  line-height: 1.5;
568
  }
569
 
570
- .popup-body ul {
571
  margin: 15px 0;
572
  padding-left: 20px;
573
  }
574
 
575
- .popup-body li {
576
  margin-bottom: 8px;
577
  line-height: 1.4;
578
  }
@@ -591,20 +589,20 @@ input[type="checkbox"] {
591
 
592
  /* Responsive design for popup */
593
  @media (max-width: 600px) {
594
- .popup-content {
595
  width: 95%;
596
  margin: 20px;
597
  }
598
 
599
- .popup-body {
600
  padding: 20px;
601
  }
602
 
603
- .popup-header {
604
  padding: 15px;
605
  }
606
 
607
- .popup-header h2 {
608
  font-size: 1.3rem;
609
  }
610
  }
@@ -625,7 +623,8 @@ input[type="checkbox"] {
625
  }
626
 
627
  /* Info Button Styles */
628
- .info-button {
 
629
  background: rgba(255, 255, 255, 0.1);
630
  border: 1px solid rgba(255, 255, 255, 0.2);
631
  border-radius: 50%;
@@ -638,9 +637,6 @@ input[type="checkbox"] {
638
  justify-content: center;
639
  gap: 2px;
640
  transition: all 0.3s ease;
641
- position: absolute;
642
- right: 0;
643
- top: 0;
644
  }
645
 
646
  .dot {
@@ -651,12 +647,12 @@ input[type="checkbox"] {
651
  transition: all 0.3s ease;
652
  }
653
 
654
- .info-button:hover {
655
  background: rgba(255, 255, 255, 0.2);
656
  transform: scale(1.1);
657
  }
658
 
659
- .info-button:hover .dot {
660
  background: #fff;
661
  }
662
 
@@ -666,7 +662,7 @@ input[type="checkbox"] {
666
  border-top: 1px solid #eee;
667
  }
668
 
669
- .popup-body a {
670
  color: #64b5f6;
671
  text-decoration: none;
672
  transition: all 0.3s ease;
@@ -674,18 +670,18 @@ input[type="checkbox"] {
674
  padding-bottom: 1px;
675
  }
676
 
677
- .popup-body a:hover {
678
  color: #90caf9;
679
  border-bottom-color: #90caf9;
680
  text-shadow: 0 0 8px rgba(144, 202, 249, 0.3);
681
  }
682
 
683
- .popup-body a:active {
684
  color: #42a5f5;
685
  transform: translateY(1px);
686
  }
687
 
688
- .popup-body a:focus {
689
  outline: 2px solid rgba(100, 181, 246, 0.5);
690
  outline-offset: 2px;
691
  border-radius: 2px;
@@ -704,7 +700,7 @@ input[type="checkbox"] {
704
  }
705
 
706
  /* Ensure links are visible in the dark theme */
707
- .popup-body strong {
708
  color: #fff;
709
  }
710
 
@@ -713,17 +709,17 @@ input[type="checkbox"] {
713
  }
714
 
715
  /* Smooth scrolling for better user experience */
716
- .popup-body {
717
  scroll-behavior: smooth;
718
  }
719
 
720
  /* Responsive link styles */
721
  @media (max-width: 600px) {
722
- .popup-body a {
723
  padding-bottom: 2px;
724
  }
725
 
726
- .popup-body a:focus {
727
  outline-offset: 1px;
728
  }
729
  }
@@ -812,8 +808,24 @@ input[type="checkbox"] {
812
  }
813
 
814
  /* Info Menu Styles */
815
- .info-menu {
816
  animation: menuFadeIn 0.2s ease-out;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
817
  }
818
 
819
  @keyframes menuFadeIn {
@@ -827,39 +839,156 @@ input[type="checkbox"] {
827
  }
828
  }
829
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
830
  /* Recent Files Menu Styles */
831
- .recent-files-menu {
832
  animation: popupFadeIn 0.3s ease-out;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
833
  }
834
 
835
  .recent-file-item:hover {
836
- background: rgba(255, 255, 255, 0.2) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
837
  }
838
 
839
  .remove-recent-file:hover {
840
- background: rgba(244, 67, 54, 0.5) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
841
  }
842
 
843
  .close-recent-menu:hover {
844
- background: rgba(255, 255, 255, 0.1) !important;
845
- border-radius: 50%;
846
  }
847
 
848
- /* Ensure header has proper positioning for the menu */
849
- .header-top {
850
- position: relative;
851
- }
852
 
853
  /* Responsive design for menus */
854
  @media (max-width: 600px) {
855
- .info-menu {
856
- right: 10px !important;
857
- left: 10px !important;
858
- min-width: auto !important;
 
859
  }
860
 
861
- .recent-files-menu {
862
  width: 95% !important;
863
  margin: 10px;
864
  }
865
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  /* Chrome, Safari, Edge, Opera */
2
  input::-webkit-outer-spin-button,
3
  input::-webkit-inner-spin-button {
4
+ -webkit-appearance: none;
5
+ margin: 0;
6
  }
7
 
8
  /* Firefox */
9
  input[type=number] {
10
+ -moz-appearance: textfield;
11
  }
12
 
13
  * {
 
402
  display: flex;
403
  flex-direction: column; /* <-- stack rows vertically */
404
  align-items: flex-start;
405
+ gap: 12px; /* spacing between rows */
406
  width: 100%;
407
  }
408
 
 
481
  }
482
 
483
 
 
 
484
  /* Popup Styles */
485
  .popup-overlay {
486
  position: fixed;
 
496
  backdrop-filter: blur(5px);
497
  }
498
 
499
+ .info-popup-content {
500
  background: linear-gradient(135deg, #1a2a6c, #b21f1f);
501
  border-radius: 15px;
502
  padding: 0;
 
520
  }
521
  }
522
 
523
+ .info-popup-header {
524
  display: flex;
525
  justify-content: space-between;
526
  align-items: center;
 
530
  }
531
 
532
 
533
+ .info-popup-header h2 {
534
  margin: 0;
535
  font-size: 1.5rem;
536
  }
 
554
  background: rgba(255, 255, 255, 0.1);
555
  }
556
 
557
+ .info-popup-body {
558
  padding: 25px;
559
  overflow-y: auto;
560
  max-height: calc(80vh - 80px);
561
  }
562
 
563
+ .info-popup-body p {
564
  margin-bottom: 15px;
565
  line-height: 1.5;
566
  }
567
 
568
+ .info-popup-body ul {
569
  margin: 15px 0;
570
  padding-left: 20px;
571
  }
572
 
573
+ .info-popup-body li {
574
  margin-bottom: 8px;
575
  line-height: 1.4;
576
  }
 
589
 
590
  /* Responsive design for popup */
591
  @media (max-width: 600px) {
592
+ .info-popup-content {
593
  width: 95%;
594
  margin: 20px;
595
  }
596
 
597
+ .info-popup-body {
598
  padding: 20px;
599
  }
600
 
601
+ .info-popup-header {
602
  padding: 15px;
603
  }
604
 
605
+ .info-popup-header h2 {
606
  font-size: 1.3rem;
607
  }
608
  }
 
623
  }
624
 
625
  /* Info Button Styles */
626
+ .menu-button {
627
+ position: relative;
628
  background: rgba(255, 255, 255, 0.1);
629
  border: 1px solid rgba(255, 255, 255, 0.2);
630
  border-radius: 50%;
 
637
  justify-content: center;
638
  gap: 2px;
639
  transition: all 0.3s ease;
 
 
 
640
  }
641
 
642
  .dot {
 
647
  transition: all 0.3s ease;
648
  }
649
 
650
+ .menu-button:hover {
651
  background: rgba(255, 255, 255, 0.2);
652
  transform: scale(1.1);
653
  }
654
 
655
+ .menu-button:hover .dot {
656
  background: #fff;
657
  }
658
 
 
662
  border-top: 1px solid #eee;
663
  }
664
 
665
+ .info-popup-body a {
666
  color: #64b5f6;
667
  text-decoration: none;
668
  transition: all 0.3s ease;
 
670
  padding-bottom: 1px;
671
  }
672
 
673
+ .info-popup-body a:hover {
674
  color: #90caf9;
675
  border-bottom-color: #90caf9;
676
  text-shadow: 0 0 8px rgba(144, 202, 249, 0.3);
677
  }
678
 
679
+ .info-popup-body a:active {
680
  color: #42a5f5;
681
  transform: translateY(1px);
682
  }
683
 
684
+ .info-popup-body a:focus {
685
  outline: 2px solid rgba(100, 181, 246, 0.5);
686
  outline-offset: 2px;
687
  border-radius: 2px;
 
700
  }
701
 
702
  /* Ensure links are visible in the dark theme */
703
+ .info-popup-body strong {
704
  color: #fff;
705
  }
706
 
 
709
  }
710
 
711
  /* Smooth scrolling for better user experience */
712
+ .info-popup-body {
713
  scroll-behavior: smooth;
714
  }
715
 
716
  /* Responsive link styles */
717
  @media (max-width: 600px) {
718
+ .info-popup-body a {
719
  padding-bottom: 2px;
720
  }
721
 
722
+ .info-popup-body a:focus {
723
  outline-offset: 1px;
724
  }
725
  }
 
808
  }
809
 
810
  /* Info Menu Styles */
811
+ .app-menu {
812
  animation: menuFadeIn 0.2s ease-out;
813
+ position: absolute;
814
+ background: linear-gradient(135deg, rgba(26, 42, 108, 0.95), rgba(178, 31, 31, 0.95));
815
+ backdrop-filter: blur(10px);
816
+ border-radius: 10px;
817
+ padding: 8px 0;
818
+ min-width: 200px;
819
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
820
+ border: 1px solid rgba(255, 255, 255, 0.2);
821
+ display: none;
822
+ z-index: 9999;
823
+ color: white;
824
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
825
+ }
826
+
827
+ .app-menu.active {
828
+ display: block;
829
  }
830
 
831
  @keyframes menuFadeIn {
 
839
  }
840
  }
841
 
842
+ .app-menu .menu-item {
843
+ padding: 12px 16px;
844
+ cursor: pointer;
845
+ transition: all 0.3s ease;
846
+ font-size: 14px;
847
+ border: none;
848
+ background: none;
849
+ width: 100%;
850
+ text-align: left;
851
+ color: white;
852
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
853
+ }
854
+
855
+ .app-menu .menu-item:last-child {
856
+ border-bottom: none;
857
+ }
858
+
859
+ .app-menu .menu-item:hover {
860
+ background: rgba(255, 255, 255, 0.1);
861
+ color: #fff;
862
+ }
863
+
864
+ .app-menu .menu-item:active {
865
+ background: rgba(255, 255, 255, 0.2);
866
+ }
867
+
868
  /* Recent Files Menu Styles */
869
+ .recent-files-content {
870
  animation: popupFadeIn 0.3s ease-out;
871
+ position: fixed;
872
+ background: linear-gradient(135deg, rgba(26, 42, 108, 0.98), rgba(178, 31, 31, 0.98));
873
+ border-radius: 15px;
874
+ padding: 25px;
875
+ max-width: 500px;
876
+ width: 90%;
877
+ max-height: 70vh;
878
+ overflow-y: auto;
879
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
880
+ border: 1px solid rgba(255, 255, 255, 0.2);
881
+ z-index: 1001;
882
+ color: white;
883
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
884
+ }
885
+
886
+ .recent-files-header {
887
+ display: flex;
888
+ justify-content: space-between;
889
+ align-items: center;
890
+ margin-bottom: 20px;
891
+ padding-bottom: 15px;
892
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2)
893
+ }
894
+
895
+ .recent-files-header h3 {
896
+ margin: 0;
897
+ font-size: 1.3rem;
898
+ color: white;
899
+ }
900
+
901
+ .recent-files-body {
902
+ text-align: center;
903
+ padding: 40px 20px;
904
+ opacity: 0.8;
905
+ }
906
+
907
+ .recent-file-item {
908
+ transition: all 0.3s ease;
909
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
910
+ display: flex;
911
+ justify-content: space-between;
912
+ align-items: center;
913
+ padding: 15px;
914
+ background: rgba(255, 255, 255, 0.08);
915
+ border-radius: 10px;
916
+ cursor: pointer;
917
  }
918
 
919
  .recent-file-item:hover {
920
+ background: rgba(255, 255, 255, 0.15) !important;
921
+ border-color: rgba(255, 255, 255, 0.3) !important;
922
+ transform: translateY(-1px);
923
+ }
924
+
925
+ .remove-recent-file {
926
+ transition: all 0.3s ease !important;
927
+ border: 1px solid rgba(244, 67, 54, 0.3) !important;
928
+ background: rgba(244, 67, 54, 0.3);
929
+ color: white;
930
+ border-radius: 6px;
931
+ padding: 6px 12px;
932
+ cursor: pointer;
933
+ font-size: 0.8rem;
934
  }
935
 
936
  .remove-recent-file:hover {
937
+ background: rgba(244, 67, 54, 0.6) !important;
938
+ border-color: rgba(244, 67, 54, 0.6) !important;
939
+ transform: scale(1.05);
940
+ }
941
+
942
+ .close-recent-menu {
943
+ transition: all 0.3s ease !important;
944
+ border-radius: 50% !important;
945
+ width: 35px !important;
946
+ height: 35px !important;
947
+ display: flex !important;
948
+ align-items: center !important;
949
+ justify-content: center !important;
950
+ background: rgba(255, 255, 255, 0.1);
951
+ border: 1px solid rgba(255, 255, 255, 0.2);
952
+ color: white;
953
+ font-size: 1.5rem;
954
+ cursor: pointer;
955
+ padding: 0;
956
  }
957
 
958
  .close-recent-menu:hover {
959
+ background: rgba(255, 255, 255, 0.15) !important;
960
+ transform: scale(1.1);
961
  }
962
 
 
 
 
 
963
 
964
  /* Responsive design for menus */
965
  @media (max-width: 600px) {
966
+ .app-menu {
967
+ right: 0 !important;
968
+ left: auto !important;
969
+ min-width: 180px !important;
970
+ top: 45px !important;
971
  }
972
 
973
+ .recent-files-content {
974
  width: 95% !important;
975
  margin: 10px;
976
  }
977
+
978
+ .menu-button {
979
+ width: 36px;
980
+ height: 36px;
981
+ }
982
+ }
983
+
984
+ /*!* Menu backdrop *!*/
985
+ /*.menu-backdrop {*/
986
+ /* position: fixed;*/
987
+ /* top: 0;*/
988
+ /* left: 0;*/
989
+ /* width: 100%;*/
990
+ /* height: 100%;*/
991
+ /* background: transparent;*/
992
+ /* z-index: 9998;*/
993
+ /* display: none;*/
994
+ /*}*/
index.html CHANGED
@@ -19,7 +19,7 @@
19
  <header>
20
  <div class="header-top">
21
  <h1>Loop Maestro</h1>
22
- <button id="infoButton" class="info-button" aria-label="About this app">
23
  <span class="dot"></span>
24
  <span class="dot"></span>
25
  <span class="dot"></span>
@@ -28,14 +28,34 @@
28
  <p class="subtitle">Upload a song, detect beats and bars, then loop any part</p>
29
  </header>
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  <!-- Info Popup -->
32
  <div id="infoPopup" class="popup-overlay">
33
- <div class="popup-content">
34
- <div class="popup-header">
35
  <h2>About Loop Maestro</h2>
36
  <button id="closePopup" class="close-button">&times;</button>
37
  </div>
38
- <div class="popup-body">
39
  <p><strong>Loop Maestro</strong> is a Progressive Web App that allows you to:</p>
40
  <ul>
41
  <li>Upload audio files</li>
@@ -271,7 +291,7 @@
271
  request.onupgradeneeded = (event) => {
272
  const db = event.target.result;
273
  if (!db.objectStoreNames.contains('fileHandles')) {
274
- db.createObjectStore('fileHandles', { keyPath: 'id' });
275
  }
276
  };
277
  });
@@ -645,121 +665,109 @@
645
  });
646
 
647
  // Enhanced Info Popup functionality with menu
648
- const infoButton = document.getElementById('infoButton');
649
- const infoPopup = document.getElementById('infoPopup');
 
 
650
  const closePopup = document.getElementById('closePopup');
 
 
 
 
 
 
 
 
 
 
 
 
651
 
652
- // Create menu for info button
653
- this.createInfoMenu();
 
 
654
 
655
  // Open popup
656
- infoButton.addEventListener('click', (e) => {
657
  e.stopPropagation();
658
- this.toggleInfoMenu();
659
  });
660
 
661
  // Close popup
662
  closePopup.addEventListener('click', function () {
663
- infoPopup.classList.remove('active');
 
 
 
 
 
664
  document.body.style.overflow = ''; // Restore scrolling
665
  });
666
 
667
  // Close popup when clicking outside content
668
- infoPopup.addEventListener('click', function (e) {
 
669
  if (e.target === infoPopup) {
670
  infoPopup.classList.remove('active');
671
  document.body.style.overflow = '';
672
  }
673
  });
674
 
 
 
 
 
 
 
 
 
 
675
  // Close popup and menu with Escape key
676
  document.addEventListener('keydown', (e) => {
677
  if (e.key === 'Escape') {
678
- infoPopup.classList.remove('active');
679
  document.body.style.overflow = '';
680
- this.hideInfoMenu();
681
  }
682
  });
683
 
684
  // Close menu when clicking outside
685
  document.addEventListener('click', () => {
686
- this.hideInfoMenu();
687
  });
688
  }
689
 
690
- createInfoMenu() {
691
- this.infoMenu = document.createElement('div');
692
- this.infoMenu.className = 'info-menu';
693
- this.infoMenu.innerHTML = `
694
- <div class="menu-item" data-action="about">About Loop Maestro</div>
695
- <div class="menu-item" data-action="recent">Recent Songs</div>
696
- `;
697
- this.infoMenu.style.cssText = `
698
- position: absolute;
699
- top: 60px;
700
- right: 0;
701
- background: rgba(255, 255, 255, 0.95);
702
- backdrop-filter: blur(10px);
703
- border-radius: 8px;
704
- padding: 8px 0;
705
- min-width: 180px;
706
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
707
- border: 1px solid rgba(255, 255, 255, 0.2);
708
- display: none;
709
- z-index: 1000;
710
- color: #333;
711
- `;
712
-
713
- // Style menu items
714
- const menuItems = this.infoMenu.querySelectorAll('.menu-item');
715
- menuItems.forEach(item => {
716
- item.style.cssText = `
717
- padding: 12px 16px;
718
- cursor: pointer;
719
- transition: background-color 0.2s;
720
- font-size: 14px;
721
- border: none;
722
- background: none;
723
- width: 100%;
724
- text-align: left;
725
- `;
726
- item.addEventListener('mouseenter', () => {
727
- item.style.backgroundColor = 'rgba(33, 150, 243, 0.1)';
728
- });
729
- item.addEventListener('mouseleave', () => {
730
- item.style.backgroundColor = 'transparent';
731
- });
732
- item.addEventListener('click', (e) => {
733
- e.stopPropagation();
734
- this.handleMenuAction(item.dataset.action);
735
- });
736
- });
737
-
738
- document.body.appendChild(this.infoMenu);
739
- }
740
-
741
- toggleInfoMenu() {
742
- if (this.infoMenu.style.display === 'block') {
743
- this.hideInfoMenu();
744
  } else {
745
- this.showInfoMenu();
746
  }
747
  }
748
 
749
- showInfoMenu() {
750
- this.infoMenu.style.display = 'block';
 
 
 
 
 
 
 
 
751
  }
752
 
753
- hideInfoMenu() {
754
- this.infoMenu.style.display = 'none';
755
  }
756
 
757
  async handleMenuAction(action) {
758
- this.hideInfoMenu();
759
 
760
  switch (action) {
761
  case 'about':
762
- document.getElementById('infoPopup').classList.add('active');
763
  document.body.style.overflow = 'hidden';
764
  break;
765
  case 'recent':
@@ -771,115 +779,61 @@
771
  async showRecentFilesMenu() {
772
  await this.loadRecentFiles();
773
 
774
- const recentFilesMenu = document.createElement('div');
775
- recentFilesMenu.className = 'recent-files-menu';
776
- recentFilesMenu.style.cssText = `
777
- position: fixed;
778
- top: 50%;
779
- left: 50%;
780
- transform: translate(-50%, -50%);
781
- background: rgba(26, 42, 108, 0.95);
782
- backdrop-filter: blur(10px);
783
- border-radius: 15px;
784
- padding: 20px;
785
- max-width: 500px;
786
- width: 90%;
787
- max-height: 70vh;
788
- overflow-y: auto;
789
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
790
- border: 1px solid rgba(255, 255, 255, 0.2);
791
- z-index: 1001;
792
- color: white;
793
- `;
794
 
795
- let menuContent = `
796
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid rgba(255, 255, 255, 0.2);">
797
- <h3 style="margin: 0;">Recent Songs</h3>
798
- <button class="close-recent-menu" style="background: none; border: none; color: white; font-size: 1.5rem; cursor: pointer; padding: 0; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center;">×</button>
799
- </div>
800
- `;
801
 
802
  if (this.recentFiles.length === 0) {
803
- menuContent += `<p style="text-align: center; opacity: 0.8;">No recent files</p>`;
 
 
 
 
 
 
804
  } else {
805
- menuContent += `<div class="recent-files-list" style="display: flex; flex-direction: column; gap: 8px;">`;
806
 
807
  for (const file of this.recentFiles) {
808
- menuContent += `
809
- <div class="recent-file-item" data-file-id="${file.id}" style="
810
- display: flex;
811
- justify-content: space-between;
812
- align-items: center;
813
- padding: 12px;
814
- background: rgba(255, 255, 255, 0.1);
815
- border-radius: 8px;
816
- cursor: pointer;
817
- transition: background-color 0.2s;
818
- ">
819
- <div>
820
- <div style="font-weight: bold;">${file.fileName}</div>
821
- <div style="font-size: 0.8rem; opacity: 0.8;">${this.formatFileSize(file.fileSize)}</div>
822
- </div>
823
- <button class="remove-recent-file" style="
824
- background: rgba(244, 67, 54, 0.3);
825
- border: none;
826
- color: white;
827
- border-radius: 4px;
828
- padding: 4px 8px;
829
- cursor: pointer;
830
- font-size: 0.8rem;
831
- ">Remove</button>
832
- </div>
833
- `;
834
  }
835
 
836
- menuContent += `</div>`;
 
837
  }
838
 
839
- recentFilesMenu.innerHTML = menuContent;
840
- document.body.appendChild(recentFilesMenu);
841
-
842
- // Add event listeners
843
- recentFilesMenu.querySelector('.close-recent-menu').addEventListener('click', () => {
844
- document.body.removeChild(recentFilesMenu);
845
- });
846
-
847
- recentFilesMenu.addEventListener('click', (e) => {
848
- if (e.target === recentFilesMenu) {
849
- document.body.removeChild(recentFilesMenu);
850
- }
851
- });
852
-
853
  // Load file when clicked
854
- recentFilesMenu.querySelectorAll('.recent-file-item').forEach(item => {
855
  item.addEventListener('click', async (e) => {
856
  if (!e.target.classList.contains('remove-recent-file')) {
857
  const fileId = item.dataset.fileId;
858
  await this.loadFileFromHandle(fileId);
859
- document.body.removeChild(recentFilesMenu);
860
  }
861
  });
862
  });
863
 
864
  // Remove file when remove button clicked
865
- recentFilesMenu.querySelectorAll('.remove-recent-file').forEach(button => {
866
  button.addEventListener('click', async (e) => {
867
  e.stopPropagation();
868
  const fileId = button.closest('.recent-file-item').dataset.fileId;
869
  await this.removeFromRecentFiles(fileId);
870
- document.body.removeChild(recentFilesMenu);
871
  await this.showRecentFilesMenu(); // Refresh the menu
872
  });
873
  });
874
 
875
- // Close with Escape key
876
- const closeHandler = (e) => {
877
- if (e.key === 'Escape') {
878
- document.body.removeChild(recentFilesMenu);
879
- document.removeEventListener('keydown', closeHandler);
880
- }
881
- };
882
- document.addEventListener('keydown', closeHandler);
883
  }
884
 
885
  async openFileWithFileSystemAPI() {
@@ -914,8 +868,8 @@
914
  }
915
 
916
  // Verify we still have permission to read the file
917
- if (await fileRecord.handle.queryPermission({ mode: 'read' }) !== 'granted') {
918
- const permission = await fileRecord.handle.requestPermission({ mode: 'read' });
919
  if (permission !== 'granted') {
920
  throw new Error('Permission denied to read the file');
921
  }
 
19
  <header>
20
  <div class="header-top">
21
  <h1>Loop Maestro</h1>
22
+ <button id="menuButton" class="menu-button" aria-label="About this app">
23
  <span class="dot"></span>
24
  <span class="dot"></span>
25
  <span class="dot"></span>
 
28
  <p class="subtitle">Upload a song, detect beats and bars, then loop any part</p>
29
  </header>
30
 
31
+ <div id="appMenu" class="app-menu" style="top:0; left:0">
32
+ <div class="menu-item" data-action="about">About Loop Maestro</div>
33
+ <div class="menu-item" data-action="recent">Recent Songs</div>
34
+ </div>
35
+
36
+ <div id="recentFilesPopup" class="popup-overlay">
37
+ <div class="recent-files-content">
38
+ <div class="recent-files-header">
39
+ <h3>🎵 Recent Songs</h3>
40
+ <button id="closeRecentMenu" class="close-recent-menu">×</button>
41
+ </div>
42
+
43
+ <div id="recentFilesBody" class="recent-files-body">
44
+ <div style="font-size: 3rem; margin-bottom: 10px;">🎵</div>
45
+ <p style="margin: 0; font-size: 1.1rem;">No recent files</p>
46
+ <p style="margin: 10px 0 0 0; font-size: 0.9rem; opacity: 0.7;">Files you open will appear here</p>
47
+ </div>
48
+ </div>
49
+ </div>
50
+
51
  <!-- Info Popup -->
52
  <div id="infoPopup" class="popup-overlay">
53
+ <div class="info-popup-content">
54
+ <div class="info-popup-header">
55
  <h2>About Loop Maestro</h2>
56
  <button id="closePopup" class="close-button">&times;</button>
57
  </div>
58
+ <div class="info-popup-body">
59
  <p><strong>Loop Maestro</strong> is a Progressive Web App that allows you to:</p>
60
  <ul>
61
  <li>Upload audio files</li>
 
291
  request.onupgradeneeded = (event) => {
292
  const db = event.target.result;
293
  if (!db.objectStoreNames.contains('fileHandles')) {
294
+ db.createObjectStore('fileHandles', {keyPath: 'id'});
295
  }
296
  };
297
  });
 
665
  });
666
 
667
  // Enhanced Info Popup functionality with menu
668
+ this.menuButton = document.getElementById('menuButton');
669
+ this.appMenu = document.getElementById('appMenu');
670
+
671
+ this.infoPopup = document.getElementById('infoPopup');
672
  const closePopup = document.getElementById('closePopup');
673
+ this.recentFilesPopup = document.getElementById('recentFilesPopup');
674
+ const closeRecentMenu = document.getElementById('closeRecentMenu');
675
+
676
+ // Menu item events
677
+ const menuItems = this.appMenu.querySelectorAll('.menu-item');
678
+ menuItems.forEach(item => {
679
+ item.addEventListener('click', (e) => {
680
+ e.stopPropagation();
681
+ this.handleMenuAction(item.dataset.action);
682
+ this.hideAppMenu();
683
+ });
684
+ });
685
 
686
+ // Backdrop for closing
687
+ this.recentFilesPopup.addEventListener('click', () => {
688
+ this.hideAppMenu();
689
+ });
690
 
691
  // Open popup
692
+ this.menuButton.addEventListener('click', (e) => {
693
  e.stopPropagation();
694
+ this.toggleAppMenu();
695
  });
696
 
697
  // Close popup
698
  closePopup.addEventListener('click', function () {
699
+ document.getElementById('infoPopup').classList.remove('active');
700
+ document.body.style.overflow = ''; // Restore scrolling
701
+ });
702
+
703
+ closeRecentMenu.addEventListener('click', function () {
704
+ document.getElementById('recentFilesPopup').classList.remove('active');
705
  document.body.style.overflow = ''; // Restore scrolling
706
  });
707
 
708
  // Close popup when clicking outside content
709
+ this.infoPopup.addEventListener('click', function (e) {
710
+ const infoPopup = document.getElementById('infoPopup')
711
  if (e.target === infoPopup) {
712
  infoPopup.classList.remove('active');
713
  document.body.style.overflow = '';
714
  }
715
  });
716
 
717
+ // Close popup when clicking outside content
718
+ this.recentFilesPopup.addEventListener('click', function (e) {
719
+ const recentFilesPopup = document.getElementById('recentFilesPopup')
720
+ if (e.target === recentFilesPopup) {
721
+ recentFilesPopup.classList.remove('active');
722
+ document.body.style.overflow = '';
723
+ }
724
+ });
725
+
726
  // Close popup and menu with Escape key
727
  document.addEventListener('keydown', (e) => {
728
  if (e.key === 'Escape') {
729
+ this.infoPopup.classList.remove('active');
730
  document.body.style.overflow = '';
731
+ this.hideAppMenu();
732
  }
733
  });
734
 
735
  // Close menu when clicking outside
736
  document.addEventListener('click', () => {
737
+ this.hideAppMenu();
738
  });
739
  }
740
 
741
+ toggleAppMenu() {
742
+ if (this.appMenu.style.display === 'block') {
743
+ this.hideAppMenu();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
744
  } else {
745
+ this.showAppMenu();
746
  }
747
  }
748
 
749
+ showAppMenu() {
750
+ const rect = this.menuButton.getBoundingClientRect();
751
+
752
+ const menuWidth = 150;
753
+
754
+ // Position menu in final location
755
+ this.appMenu.style.top = `${rect.bottom + window.scrollY}px`;
756
+ this.appMenu.style.left = `${rect.right - menuWidth}px`;
757
+
758
+ appMenu.classList.add('active');
759
  }
760
 
761
+ hideAppMenu() {
762
+ this.appMenu.classList.remove('active');
763
  }
764
 
765
  async handleMenuAction(action) {
766
+ this.hideAppMenu();
767
 
768
  switch (action) {
769
  case 'about':
770
+ this.infoPopup.classList.add('active');
771
  document.body.style.overflow = 'hidden';
772
  break;
773
  case 'recent':
 
779
  async showRecentFilesMenu() {
780
  await this.loadRecentFiles();
781
 
782
+ const recentFilesBody = document.getElementById('recentFilesBody');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
783
 
784
+ // Clear existing content first
785
+ recentFilesBody.innerHTML = '';
 
 
 
 
786
 
787
  if (this.recentFiles.length === 0) {
788
+ recentFilesBody.innerHTML = `
789
+ <div style="text-align: center; padding: 40px 20px; opacity: 0.8;">
790
+ <div style="font-size: 3rem; margin-bottom: 10px;">🎵</div>
791
+ <p style="margin: 0; font-size: 1.1rem;">No recent files</p>
792
+ <p style="margin: 10px 0 0 0; font-size: 0.9rem; opacity: 0.7;">Files you open will appear here</p>
793
+ </div>
794
+ `;
795
  } else {
796
+ let filesHTML = '<div class="recent-files-list" style="display: flex; flex-direction: column; gap: 10px;">';
797
 
798
  for (const file of this.recentFiles) {
799
+ filesHTML += `
800
+ <div class="recent-file-item" data-file-id="${file.id}">
801
+ <div style="flex: 1;">
802
+ <div style="font-weight: bold; font-size: 1rem; margin-bottom: 4px;">${file.fileName}</div>
803
+ <div style="font-size: 0.8rem; opacity: 0.8;">${this.formatFileSize(file.fileSize)}</div>
804
+ </div>
805
+ <button class="remove-recent-file">Remove</button>
806
+ </div>
807
+ `;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
808
  }
809
 
810
+ filesHTML += '</div>';
811
+ recentFilesBody.innerHTML = filesHTML;
812
  }
813
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
814
  // Load file when clicked
815
+ recentFilesBody.querySelectorAll('.recent-file-item').forEach(item => {
816
  item.addEventListener('click', async (e) => {
817
  if (!e.target.classList.contains('remove-recent-file')) {
818
  const fileId = item.dataset.fileId;
819
  await this.loadFileFromHandle(fileId);
820
+ this.recentFilesPopup.classList.remove('active');
821
  }
822
  });
823
  });
824
 
825
  // Remove file when remove button clicked
826
+ recentFilesBody.querySelectorAll('.remove-recent-file').forEach(button => {
827
  button.addEventListener('click', async (e) => {
828
  e.stopPropagation();
829
  const fileId = button.closest('.recent-file-item').dataset.fileId;
830
  await this.removeFromRecentFiles(fileId);
 
831
  await this.showRecentFilesMenu(); // Refresh the menu
832
  });
833
  });
834
 
835
+ this.recentFilesPopup.classList.add('active');
836
+ document.body.style.overflow = 'hidden';
 
 
 
 
 
 
837
  }
838
 
839
  async openFileWithFileSystemAPI() {
 
868
  }
869
 
870
  // Verify we still have permission to read the file
871
+ if (await fileRecord.handle.queryPermission({mode: 'read'}) !== 'granted') {
872
+ const permission = await fileRecord.handle.requestPermission({mode: 'read'});
873
  if (permission !== 'granted') {
874
  throw new Error('Permission denied to read the file');
875
  }
sw.js CHANGED
@@ -1,5 +1,5 @@
1
  // Use a version that you can update with each release
2
- const APP_VERSION = '0.1.6';
3
  const CACHE_NAME = `my-pwa-cache-${APP_VERSION}`;
4
 
5
  // List of files to cache
 
1
  // Use a version that you can update with each release
2
+ const APP_VERSION = '0.1.7';
3
  const CACHE_NAME = `my-pwa-cache-${APP_VERSION}`;
4
 
5
  // List of files to cache