Freefall commited on
Commit
cf09a43
·
verified ·
1 Parent(s): 3a316b9

added features

Browse files

added a lot of features and bug fixes

Files changed (1) hide show
  1. index.html +518 -59
index.html CHANGED
@@ -253,11 +253,18 @@
253
  display: block;
254
  }
255
 
 
 
 
 
 
 
256
  .progress-bar {
257
  height: 4px;
258
  background: #0a0a1a;
259
  border-radius: 2px;
260
  overflow: hidden;
 
261
  }
262
 
263
  .progress-fill {
@@ -403,6 +410,65 @@
403
  margin-bottom: 12px;
404
  cursor: pointer;
405
  user-select: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
  }
407
 
408
  .checkbox-container input {
@@ -457,8 +523,12 @@
457
  }
458
 
459
  .scrollbar-hide {
460
- -ms-overflow-style: none;
461
- scrollbar-width: none;
 
 
 
 
462
  }
463
 
464
  /* Cyberpunk scanline effect */
@@ -615,7 +685,7 @@
615
  <div id="settings-panel" class="settings-panel">
616
  <div class="flex justify-between items-center mb-4 border-b border-cyber-primary pb-2">
617
  <h3 class="text-xl">SYSTEM SETTINGS</h3>
618
- <button id="close-settings" class="text-cyber-primary hover:text-cyber-accent">
619
  <i class="fas fa-times"></i>
620
  </button>
621
  </div>
@@ -660,9 +730,26 @@
660
  </button>
661
  </div>
662
  </div>
663
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664
  <!-- Main Container -->
665
- <div class="container mx-auto p-4 max-w-6xl">
666
  <!-- Header -->
667
  <div class="flex justify-between items-center mb-6">
668
  <div class="glitch-effect" data-text="WONTV1EW L33T">
@@ -679,10 +766,10 @@
679
  <a href="#" id="open-folder-btn"><i class="fas fa-folder mr-2"></i> FOLDER</a>
680
  </div>
681
  </div>
682
- <input type="file" id="file-input" class="hidden" multiple accept="image/*,video/*">
683
- <input type="file" id="folder-input" class="hidden" webkitdirectory directory multiple accept="image/*,video/*">
684
 
685
- <button id="settings-btn" class="cyber-button bg-cyber-dark px-4 py-2 rounded cyber-border hover:shadow-cyber tooltip" data-tooltip="SETTINGS">
686
  <i class="fas fa-cog"></i>
687
  </button>
688
  </div>
@@ -691,7 +778,7 @@
691
  <!-- Main Content -->
692
  <div class="cyber-bg cyber-border rounded-xl overflow-hidden">
693
  <!-- Media Display Area -->
694
- <div class="media-area relative h-[70vh] bg-black flex items-center justify-center">
695
  <div id="media-display" class="w-full h-full flex items-center justify-center relative">
696
  <div id="no-media" class="text-cyber-secondary text-center p-8">
697
  <i class="fas fa-images text-6xl mb-4 text-cyber-accent pulse"></i>
@@ -701,7 +788,7 @@
701
  </div>
702
 
703
  <div id="image-container" class="hidden absolute inset-0 overflow-auto cursor-grab">
704
- <div id="image-wrapper" class="media-container">
705
  <img id="current-image" class="max-w-full max-h-full" src="" alt="">
706
  </div>
707
  </div>
@@ -723,32 +810,36 @@
723
 
724
  <!-- Zoom Controls -->
725
  <div id="zoom-controls" class="hidden absolute bottom-4 right-4 bg-cyber-dark bg-opacity-90 text-cyber-primary p-2 rounded cyber-border">
726
- <button id="zoom-in" class="p-2 hover:text-cyber-accent tooltip" data-tooltip="ZOOM IN (+)">
727
  <i class="fas fa-search-plus"></i>
728
  </button>
729
  <div class="h-px bg-cyber-primary my-1"></div>
730
- <button id="zoom-out" class="p-2 hover:text-cyber-accent tooltip" data-tooltip="ZOOM OUT (-)">
731
  <i class="fas fa-search-minus"></i>
732
  </button>
733
  <div class="h-px bg-cyber-primary my-1"></div>
734
- <button id="zoom-reset" class="p-2 hover:text-cyber-accent tooltip" data-tooltip="RESET ZOOM (0)">
 
 
 
 
735
  <i class="fas fa-expand"></i>
736
  </button>
737
  </div>
738
 
739
  <!-- Video Controls -->
740
  <div id="video-controls" class="hidden absolute bottom-4 left-4 right-4 bg-cyber-dark bg-opacity-90 text-cyber-primary p-3 rounded cyber-border flex items-center justify-between">
741
- <button id="play-pause" class="p-2 hover:text-cyber-accent tooltip" data-tooltip="PLAY/PAUSE (SPACE)">
742
  <i class="fas fa-play" id="play-icon"></i>
743
  </button>
744
- <div class="flex-1 mx-4">
745
  <div class="progress-bar">
746
  <div id="progress-fill" class="progress-fill" style="width: 0%"></div>
747
  </div>
748
  </div>
749
  <div class="flex items-center space-x-3">
750
  <i class="fas fa-volume-down"></i>
751
- <input type="range" id="volume-slider" class="volume-slider" min="0" max="1" step="0.01" value="1">
752
  <i class="fas fa-volume-up"></i>
753
  </div>
754
  </div>
@@ -784,7 +875,7 @@
784
  <div class="flex items-center space-x-4">
785
  <div class="flex items-center">
786
  <span class="text-sm mr-2">SPEED:</span>
787
- <select id="slideshow-speed" class="bg-cyber-dark border border-cyber-primary rounded px-2 py-1 text-sm text-cyber-primary">
788
  <option value="1000">1 SEC</option>
789
  <option value="2000" selected>2 SEC</option>
790
  <option value="3000">3 SEC</option>
@@ -801,7 +892,7 @@
801
  <div class="flex items-center space-x-4">
802
  <div class="flex items-center">
803
  <span class="text-sm mr-2">SIZE:</span>
804
- <select id="image-size" class="bg-cyber-dark border border-cyber-primary rounded px-2 py-1 text-sm text-cyber-primary">
805
  <option value="contain">FIT</option>
806
  <option value="cover">FILL</option>
807
  <option value="original">ORIGINAL</option>
@@ -810,16 +901,30 @@
810
  <button id="toggle-sidebar" class="cyber-button bg-cyber-dark px-3 py-1 rounded cyber-border hover:shadow-cyber tooltip" data-tooltip="TOGGLE THUMBNAILS (T)">
811
  <i class="fas fa-th mr-1"></i> THUMBNAILS
812
  </button>
 
 
 
813
  </div>
814
  </div>
815
 
816
  <!-- Status Bar -->
817
- <div class="mt-4 flex justify-between items-center text-sm">
818
- <div>
819
- <span id="current-index" class="text-cyber-secondary">0/0</span>
820
- <span id="media-name" class="ml-2 text-cyber-primary"></span>
 
 
 
 
 
 
 
 
 
 
 
 
821
  </div>
822
- <div id="status-message" class="text-cyber-accent pulse"></div>
823
  </div>
824
  </div>
825
  </div>
@@ -847,6 +952,7 @@
847
  const zoomInBtn = document.getElementById('zoom-in');
848
  const zoomOutBtn = document.getElementById('zoom-out');
849
  const zoomResetBtn = document.getElementById('zoom-reset');
 
850
  const zoomControls = document.getElementById('zoom-controls');
851
  const playPauseBtn = document.getElementById('play-pause');
852
  const playPauseBottomBtn = document.getElementById('play-pause-bottom');
@@ -855,9 +961,19 @@
855
  const playIconBottom = document.getElementById('play-icon-bottom');
856
  const toggleSlideshowBtn = document.getElementById('toggle-slideshow');
857
  const toggleSidebarBtn = document.getElementById('toggle-sidebar');
 
858
  const currentIndexDisplay = document.getElementById('current-index');
859
  const mediaNameDisplay = document.getElementById('media-name');
 
 
 
 
 
 
 
 
860
  const progressFill = document.getElementById('progress-fill');
 
861
  const videoControls = document.getElementById('video-controls');
862
  const volumeSlider = document.getElementById('volume-slider');
863
  const imageSizeSelect = document.getElementById('image-size');
@@ -884,6 +1000,11 @@
884
  let skipVideosInSlideshow = true;
885
  let cyberEffectsEnabled = true;
886
  let matrixRainEnabled = true;
 
 
 
 
 
887
 
888
  // Initialize settings from localStorage
889
  loadSettings();
@@ -954,6 +1075,7 @@
954
  zoomInBtn.addEventListener('click', () => zoomImage(1.2));
955
  zoomOutBtn.addEventListener('click', () => zoomImage(0.8));
956
  zoomResetBtn.addEventListener('click', resetZoom);
 
957
 
958
  // Playback controls
959
  playPauseBtn.addEventListener('click', togglePlayPause);
@@ -971,9 +1093,20 @@
971
  playIconBottom.className = 'fas fa-play';
972
  });
973
  currentVideo.addEventListener('ended', goToNext);
974
- volumeSlider.addEventListener('input', () => {
975
  currentVideo.volume = volumeSlider.value;
976
  });
 
 
 
 
 
 
 
 
 
 
 
977
 
978
  // Slideshow controls
979
  toggleSlideshowBtn.addEventListener('click', toggleSlideshow);
@@ -985,6 +1118,13 @@
985
  applyImageSize();
986
  }
987
  });
 
 
 
 
 
 
 
988
 
989
  // Mouse wheel behavior
990
  mediaDisplay.addEventListener('wheel', handleWheelEvent);
@@ -1024,6 +1164,12 @@
1024
  matrixRainEnabled = e.target.checked;
1025
  toggleMatrixRain(matrixRainEnabled);
1026
  });
 
 
 
 
 
 
1027
 
1028
  // Functions
1029
  function handleFileSelection(e) {
@@ -1049,6 +1195,9 @@
1049
  videoControls.classList.add('hidden');
1050
  currentIndexDisplay.textContent = '0/0';
1051
  mediaNameDisplay.textContent = '';
 
 
 
1052
  return;
1053
  }
1054
 
@@ -1064,10 +1213,28 @@
1064
  videoContainer.classList.add('hidden');
1065
  zoomControls.classList.remove('hidden');
1066
  videoControls.classList.add('hidden');
1067
- resetZoom();
1068
- applyImageSize();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1069
  };
1070
- reader.readAsDataURL(file);
1071
  } else if (file.type.startsWith('video/')) {
1072
  // Display video
1073
  const reader = new FileReader();
@@ -1080,6 +1247,9 @@
1080
  currentVideo.volume = volumeSlider.value;
1081
  playIcon.className = 'fas fa-play';
1082
  playIconBottom.className = 'fas fa-play';
 
 
 
1083
  };
1084
  reader.readAsDataURL(file);
1085
  }
@@ -1142,26 +1312,56 @@
1142
 
1143
  function goToPrevious() {
1144
  if (mediaFiles.length === 0) return;
1145
-
1146
- currentIndex = (currentIndex - 1 + mediaFiles.length) % mediaFiles.length;
1147
- displayCurrentMedia();
1148
-
1149
- // If slideshow is running and current media is video and skip videos is enabled
1150
- if (isSlideshowRunning && skipVideosInSlideshow && mediaFiles[currentIndex].type.startsWith('video/')) {
1151
- setTimeout(goToPrevious, 100); // Skip this video
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1152
  }
1153
  }
1154
 
1155
  function goToNext() {
1156
  if (mediaFiles.length === 0) return;
1157
-
1158
- currentIndex = (currentIndex + 1) % mediaFiles.length;
1159
- displayCurrentMedia();
1160
-
1161
- // If slideshow is running and current media is video and skip videos is enabled
1162
- if (isSlideshowRunning && skipVideosInSlideshow && mediaFiles[currentIndex].type.startsWith('video/')) {
1163
- setTimeout(goToNext, 100); // Skip this video
1164
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1165
  }
1166
 
1167
  function zoomImage(factor) {
@@ -1216,23 +1416,35 @@
1216
  }
1217
 
1218
  function togglePlayPause() {
1219
- if (!mediaFiles[currentIndex]?.type.startsWith('video/')) {
 
 
 
1220
  if (!isSlideshowRunning) {
1221
- toggleSlideshow();
1222
  } else {
1223
  stopSlideshow();
1224
  }
1225
  return;
1226
  }
1227
-
1228
- if (currentVideo.paused) {
1229
- currentVideo.play();
1230
- playIcon.className = 'fas fa-pause';
1231
- playIconBottom.className = 'fas fa-pause';
1232
- } else {
1233
- currentVideo.pause();
1234
- playIcon.className = 'fas fa-play';
1235
- playIconBottom.className = 'fas fa-play';
 
 
 
 
 
 
 
 
 
1236
  }
1237
  }
1238
 
@@ -1250,8 +1462,11 @@
1250
  }
1251
 
1252
  function updateVideoProgress() {
1253
- const percent = (currentVideo.currentTime / currentVideo.duration) * 100;
1254
- progressFill.style.width = `${percent}%`;
 
 
 
1255
  }
1256
 
1257
  function toggleSlideshow() {
@@ -1368,17 +1583,29 @@
1368
  currentVideo.volume = Math.max(0, currentVideo.volume - 0.1);
1369
  volumeSlider.value = currentVideo.volume;
1370
  }
1371
- break;
1372
  case 't':
1373
  case 'T':
1374
  toggleThumbnailSidebar();
1375
  break;
 
 
 
 
 
 
 
 
1376
  case 's':
1377
  case 'S':
1378
  toggleSlideshow();
1379
  break;
1380
  case 'Escape':
1381
- closeSettings();
 
 
 
 
1382
  break;
1383
  }
1384
  }
@@ -1483,7 +1710,239 @@
1483
  matrixRainContainer.style.display = 'none';
1484
  }
1485
  }
1486
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1487
  </script>
1488
- <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Freefall/media-viewer-pro" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1489
  </html>
 
253
  display: block;
254
  }
255
 
256
+ .progress-bar-container { /* Added container */
257
+ height: 10px; /* Increased height for easier clicking */
258
+ padding: 3px 0; /* Vertical padding */
259
+ cursor: pointer;
260
+ width: 100%;
261
+ }
262
  .progress-bar {
263
  height: 4px;
264
  background: #0a0a1a;
265
  border-radius: 2px;
266
  overflow: hidden;
267
+ pointer-events: none; /* Prevent clicks on the bar itself */
268
  }
269
 
270
  .progress-fill {
 
410
  margin-bottom: 12px;
411
  cursor: pointer;
412
  user-select: none;
413
+ -webkit-user-select: none; /* Safari */
414
+ }
415
+
416
+ /* Metadata Panel Styles (similar to settings) */
417
+ .metadata-panel {
418
+ position: fixed;
419
+ top: 50%;
420
+ left: 50%;
421
+ transform: translate(-50%, -50%);
422
+ background: #0a0a1a;
423
+ border: 1px solid #00ff9d;
424
+ box-shadow: 0 0 20px rgba(0, 255, 157, 0.5);
425
+ z-index: 101; /* Above settings */
426
+ padding: 20px;
427
+ border-radius: 5px;
428
+ max-width: 80vw; /* Wider */
429
+ width: 90%;
430
+ max-height: 80vh; /* Limit height */
431
+ opacity: 0;
432
+ pointer-events: none;
433
+ transition: all 0.3s ease;
434
+ display: flex; /* Use flex for layout */
435
+ flex-direction: column;
436
+ }
437
+
438
+ .metadata-panel.active {
439
+ opacity: 1;
440
+ pointer-events: all;
441
+ }
442
+
443
+ .metadata-content {
444
+ flex-grow: 1; /* Allow content to take available space */
445
+ overflow-y: auto; /* Enable vertical scroll */
446
+ background: #050510;
447
+ padding: 10px;
448
+ border: 1px solid #00b8ff;
449
+ border-radius: 3px;
450
+ font-size: 0.8rem;
451
+ white-space: pre-wrap; /* Wrap long lines */
452
+ word-break: break-all; /* Break long words/strings */
453
+ color: #00ff9d; /* Match body text */
454
+ }
455
+
456
+ .metadata-overlay {
457
+ position: fixed;
458
+ top: 0;
459
+ left: 0;
460
+ right: 0;
461
+ bottom: 0;
462
+ background: rgba(0, 0, 0, 0.8); /* Darker overlay */
463
+ z-index: 100; /* Below panel, above rest */
464
+ opacity: 0;
465
+ pointer-events: none;
466
+ transition: opacity 0.3s ease;
467
+ }
468
+
469
+ .metadata-overlay.active {
470
+ opacity: 1;
471
+ pointer-events: all;
472
  }
473
 
474
  .checkbox-container input {
 
523
  }
524
 
525
  .scrollbar-hide {
526
+ -ms-overflow-style: none; /* IE and Edge */
527
+ scrollbar-width: none; /* Firefox */
528
+ cursor: grab; /* Add grab cursor */
529
+ }
530
+ .scrollbar-hide:active {
531
+ cursor: grabbing; /* Add grabbing cursor when dragging */
532
  }
533
 
534
  /* Cyberpunk scanline effect */
 
685
  <div id="settings-panel" class="settings-panel">
686
  <div class="flex justify-between items-center mb-4 border-b border-cyber-primary pb-2">
687
  <h3 class="text-xl">SYSTEM SETTINGS</h3>
688
+ <button id="close-settings" class="text-cyber-primary hover:text-cyber-accent" title="Close Settings">
689
  <i class="fas fa-times"></i>
690
  </button>
691
  </div>
 
730
  </button>
731
  </div>
732
  </div>
733
+
734
+ <!-- Metadata Panel -->
735
+ <div id="metadata-overlay" class="metadata-overlay"></div>
736
+ <div id="metadata-panel" class="metadata-panel">
737
+ <div class="flex justify-between items-center mb-4 border-b border-cyber-primary pb-2">
738
+ <h3 class="text-xl">FULL METADATA</h3>
739
+ <button id="close-metadata" class="text-cyber-primary hover:text-cyber-accent" title="Close Metadata">
740
+ <i class="fas fa-times"></i>
741
+ </button>
742
+ </div>
743
+ <pre id="full-metadata-content" class="metadata-content scrollbar-hide"></pre>
744
+ <div class="mt-4 pt-2 border-t border-cyber-primary text-right">
745
+ <button id="copy-metadata-btn" class="cyber-button bg-cyber-dark px-3 py-1 rounded cyber-border hover:shadow-cyber tooltip" data-tooltip="COPY METADATA" title="Copy Metadata">
746
+ <i class="fas fa-copy mr-1"></i> COPY
747
+ </button>
748
+ </div>
749
+ </div>
750
+
751
  <!-- Main Container -->
752
+ <div class="container mx-auto p-4">
753
  <!-- Header -->
754
  <div class="flex justify-between items-center mb-6">
755
  <div class="glitch-effect" data-text="WONTV1EW L33T">
 
766
  <a href="#" id="open-folder-btn"><i class="fas fa-folder mr-2"></i> FOLDER</a>
767
  </div>
768
  </div>
769
+ <input type="file" id="file-input" class="hidden" multiple accept="image/*,video/*" aria-label="File Input">
770
+ <input type="file" id="folder-input" class="hidden" webkitdirectory directory multiple accept="image/*,video/*" aria-label="Folder Input">
771
 
772
+ <button id="settings-btn" class="cyber-button bg-cyber-dark px-4 py-2 rounded cyber-border hover:shadow-cyber tooltip" data-tooltip="SETTINGS" title="Open Settings">
773
  <i class="fas fa-cog"></i>
774
  </button>
775
  </div>
 
778
  <!-- Main Content -->
779
  <div class="cyber-bg cyber-border rounded-xl overflow-hidden">
780
  <!-- Media Display Area -->
781
+ <div class="media-area relative h-[75vh] bg-black flex items-center justify-center">
782
  <div id="media-display" class="w-full h-full flex items-center justify-center relative">
783
  <div id="no-media" class="text-cyber-secondary text-center p-8">
784
  <i class="fas fa-images text-6xl mb-4 text-cyber-accent pulse"></i>
 
788
  </div>
789
 
790
  <div id="image-container" class="hidden absolute inset-0 overflow-auto cursor-grab">
791
+ <div id="image-wrapper" class="media-container w-full h-full flex items-center justify-center">
792
  <img id="current-image" class="max-w-full max-h-full" src="" alt="">
793
  </div>
794
  </div>
 
810
 
811
  <!-- Zoom Controls -->
812
  <div id="zoom-controls" class="hidden absolute bottom-4 right-4 bg-cyber-dark bg-opacity-90 text-cyber-primary p-2 rounded cyber-border">
813
+ <button id="zoom-in" class="p-2 hover:text-cyber-accent tooltip" data-tooltip="ZOOM IN (+)" title="Zoom In">
814
  <i class="fas fa-search-plus"></i>
815
  </button>
816
  <div class="h-px bg-cyber-primary my-1"></div>
817
+ <button id="zoom-out" class="p-2 hover:text-cyber-accent tooltip" data-tooltip="ZOOM OUT (-)" title="Zoom Out">
818
  <i class="fas fa-search-minus"></i>
819
  </button>
820
  <div class="h-px bg-cyber-primary my-1"></div>
821
+ <button id="copy-image-btn" class="p-2 hover:text-cyber-accent tooltip" data-tooltip="COPY IMAGE (C)" title="Copy Image">
822
+ <i class="fas fa-copy"></i>
823
+ </button>
824
+ <div class="h-px bg-cyber-primary my-1"></div>
825
+ <button id="zoom-reset" class="p-2 hover:text-cyber-accent tooltip" data-tooltip="RESET ZOOM (0)" title="Reset Zoom">
826
  <i class="fas fa-expand"></i>
827
  </button>
828
  </div>
829
 
830
  <!-- Video Controls -->
831
  <div id="video-controls" class="hidden absolute bottom-4 left-4 right-4 bg-cyber-dark bg-opacity-90 text-cyber-primary p-3 rounded cyber-border flex items-center justify-between">
832
+ <button id="play-pause" class="p-2 hover:text-cyber-accent tooltip" data-tooltip="PLAY/PAUSE (SPACE)" title="Play/Pause">
833
  <i class="fas fa-play" id="play-icon"></i>
834
  </button>
835
+ <div class="flex-1 mx-4 progress-bar-container"> <!-- Added container -->
836
  <div class="progress-bar">
837
  <div id="progress-fill" class="progress-fill" style="width: 0%"></div>
838
  </div>
839
  </div>
840
  <div class="flex items-center space-x-3">
841
  <i class="fas fa-volume-down"></i>
842
+ <input type="range" id="volume-slider" class="volume-slider" min="0" max="1" step="0.01" value="1" aria-label="Volume">
843
  <i class="fas fa-volume-up"></i>
844
  </div>
845
  </div>
 
875
  <div class="flex items-center space-x-4">
876
  <div class="flex items-center">
877
  <span class="text-sm mr-2">SPEED:</span>
878
+ <select id="slideshow-speed" class="bg-cyber-dark border border-cyber-primary rounded px-2 py-1 text-sm text-cyber-primary" aria-label="Slideshow Speed">
879
  <option value="1000">1 SEC</option>
880
  <option value="2000" selected>2 SEC</option>
881
  <option value="3000">3 SEC</option>
 
892
  <div class="flex items-center space-x-4">
893
  <div class="flex items-center">
894
  <span class="text-sm mr-2">SIZE:</span>
895
+ <select id="image-size" class="bg-cyber-dark border border-cyber-primary rounded px-2 py-1 text-sm text-cyber-primary" aria-label="Image Size">
896
  <option value="contain">FIT</option>
897
  <option value="cover">FILL</option>
898
  <option value="original">ORIGINAL</option>
 
901
  <button id="toggle-sidebar" class="cyber-button bg-cyber-dark px-3 py-1 rounded cyber-border hover:shadow-cyber tooltip" data-tooltip="TOGGLE THUMBNAILS (T)">
902
  <i class="fas fa-th mr-1"></i> THUMBNAILS
903
  </button>
904
+ <button id="toggle-fullscreen" class="cyber-button bg-cyber-dark px-3 py-1 rounded cyber-border hover:shadow-cyber tooltip" data-tooltip="FULLSCREEN (F)">
905
+ <i class="fas fa-expand mr-1"></i><span class="button-text"> FULLSCREEN</span>
906
+ </button>
907
  </div>
908
  </div>
909
 
910
  <!-- Status Bar -->
911
+ <div class="mt-4 text-xs space-y-1">
912
+ <div class="flex justify-between items-center">
913
+ <div>
914
+ <span id="current-index" class="text-cyber-secondary mr-2">0/0</span>
915
+ <span id="media-name" class="text-cyber-primary"></span>
916
+ </div>
917
+ <div id="status-message" class="text-cyber-accent pulse"></div>
918
+ </div>
919
+ <div class="flex justify-between items-center">
920
+ <span id="media-dimensions" class="text-cyber-secondary mr-4"></span>
921
+ <div class="flex items-center min-w-0"> <!-- Flex container for metadata and button -->
922
+ <span id="media-metadata" class="text-cyber-secondary truncate mr-2" title="Metadata"></span>
923
+ <button id="view-metadata-btn" class="hidden text-cyber-accent hover:text-cyber-primary text-xs tooltip" data-tooltip="VIEW FULL METADATA" title="View Full Metadata">
924
+ <i class="fas fa-info-circle"></i>
925
+ </button>
926
+ </div>
927
  </div>
 
928
  </div>
929
  </div>
930
  </div>
 
952
  const zoomInBtn = document.getElementById('zoom-in');
953
  const zoomOutBtn = document.getElementById('zoom-out');
954
  const zoomResetBtn = document.getElementById('zoom-reset');
955
+ const copyImageBtn = document.getElementById('copy-image-btn');
956
  const zoomControls = document.getElementById('zoom-controls');
957
  const playPauseBtn = document.getElementById('play-pause');
958
  const playPauseBottomBtn = document.getElementById('play-pause-bottom');
 
961
  const playIconBottom = document.getElementById('play-icon-bottom');
962
  const toggleSlideshowBtn = document.getElementById('toggle-slideshow');
963
  const toggleSidebarBtn = document.getElementById('toggle-sidebar');
964
+ const toggleFullscreenBtn = document.getElementById('toggle-fullscreen');
965
  const currentIndexDisplay = document.getElementById('current-index');
966
  const mediaNameDisplay = document.getElementById('media-name');
967
+ const mediaDimensionsDisplay = document.getElementById('media-dimensions');
968
+ const mediaMetadataDisplay = document.getElementById('media-metadata');
969
+ const viewMetadataBtn = document.getElementById('view-metadata-btn');
970
+ const metadataOverlay = document.getElementById('metadata-overlay');
971
+ const metadataPanel = document.getElementById('metadata-panel');
972
+ const fullMetadataContent = document.getElementById('full-metadata-content');
973
+ const closeMetadataBtn = document.getElementById('close-metadata');
974
+ const copyMetadataBtn = document.getElementById('copy-metadata-btn');
975
  const progressFill = document.getElementById('progress-fill');
976
+ const progressBarContainer = document.querySelector('.progress-bar-container');
977
  const videoControls = document.getElementById('video-controls');
978
  const volumeSlider = document.getElementById('volume-slider');
979
  const imageSizeSelect = document.getElementById('image-size');
 
1000
  let skipVideosInSlideshow = true;
1001
  let cyberEffectsEnabled = true;
1002
  let matrixRainEnabled = true;
1003
+ let isScrubbingVideo = false; // Added for video scrubbing
1004
+ let isDraggingThumbnails = false; // Added for thumbnail dragging
1005
+ let thumbnailDragStartX = 0; // Added for thumbnail dragging
1006
+ let thumbnailScrollLeftStart = 0; // Added for thumbnail dragging
1007
+ let fullMetadataText = ''; // Added to store full metadata
1008
 
1009
  // Initialize settings from localStorage
1010
  loadSettings();
 
1075
  zoomInBtn.addEventListener('click', () => zoomImage(1.2));
1076
  zoomOutBtn.addEventListener('click', () => zoomImage(0.8));
1077
  zoomResetBtn.addEventListener('click', resetZoom);
1078
+ copyImageBtn.addEventListener('click', copyImageToClipboard);
1079
 
1080
  // Playback controls
1081
  playPauseBtn.addEventListener('click', togglePlayPause);
 
1093
  playIconBottom.className = 'fas fa-play';
1094
  });
1095
  currentVideo.addEventListener('ended', goToNext);
1096
+ volumeSlider.addEventListener('input', () => { // Keep this for slider input
1097
  currentVideo.volume = volumeSlider.value;
1098
  });
1099
+ // Video Scrubbing Listeners
1100
+ progressBarContainer.addEventListener('mousedown', startVideoScrub);
1101
+ document.addEventListener('mousemove', videoScrub);
1102
+ document.addEventListener('mouseup', stopVideoScrub);
1103
+ document.addEventListener('mouseleave', stopVideoScrub); // Stop if mouse leaves window
1104
+
1105
+ // Thumbnail Drag Scrolling Listeners
1106
+ thumbnailSidebar.addEventListener('mousedown', startThumbnailDrag);
1107
+ document.addEventListener('mousemove', thumbnailDrag); // Listen on document for wider drag area
1108
+ document.addEventListener('mouseup', stopThumbnailDrag);
1109
+ document.addEventListener('mouseleave', stopThumbnailDrag); // Stop if mouse leaves window
1110
 
1111
  // Slideshow controls
1112
  toggleSlideshowBtn.addEventListener('click', toggleSlideshow);
 
1118
  applyImageSize();
1119
  }
1120
  });
1121
+
1122
+ // Fullscreen controls
1123
+ toggleFullscreenBtn.addEventListener('click', toggleFullScreen);
1124
+ document.addEventListener('fullscreenchange', handleFullscreenChange);
1125
+ document.addEventListener('webkitfullscreenchange', handleFullscreenChange); // Safari
1126
+ document.addEventListener('mozfullscreenchange', handleFullscreenChange); // Firefox
1127
+ document.addEventListener('MSFullscreenChange', handleFullscreenChange); // IE/Edge
1128
 
1129
  // Mouse wheel behavior
1130
  mediaDisplay.addEventListener('wheel', handleWheelEvent);
 
1164
  matrixRainEnabled = e.target.checked;
1165
  toggleMatrixRain(matrixRainEnabled);
1166
  });
1167
+
1168
+ // Metadata Panel Listeners
1169
+ viewMetadataBtn.addEventListener('click', showMetadataPanel);
1170
+ closeMetadataBtn.addEventListener('click', hideMetadataPanel);
1171
+ metadataOverlay.addEventListener('click', hideMetadataPanel);
1172
+ copyMetadataBtn.addEventListener('click', copyFullMetadata);
1173
 
1174
  // Functions
1175
  function handleFileSelection(e) {
 
1195
  videoControls.classList.add('hidden');
1196
  currentIndexDisplay.textContent = '0/0';
1197
  mediaNameDisplay.textContent = '';
1198
+ mediaDimensionsDisplay.textContent = ''; // Clear dimensions
1199
+ mediaMetadataDisplay.textContent = ''; // Clear metadata
1200
+ viewMetadataBtn.classList.add('hidden'); // Hide view button
1201
  return;
1202
  }
1203
 
 
1213
  videoContainer.classList.add('hidden');
1214
  zoomControls.classList.remove('hidden');
1215
  videoControls.classList.add('hidden');
1216
+ resetZoom(); // Reset zoom/pan first
1217
+ applyImageSize(); // Apply selected size mode
1218
+
1219
+ // Get dimensions after image loads
1220
+ currentImage.onload = () => {
1221
+ mediaDimensionsDisplay.textContent = `Dimensions: ${currentImage.naturalWidth} x ${currentImage.naturalHeight}`;
1222
+ // Attempt to extract metadata for PNGs
1223
+ if (file.type === 'image/png') {
1224
+ extractAndDisplayPngMetadata(file);
1225
+ } else {
1226
+ mediaMetadataDisplay.textContent = '';
1227
+ viewMetadataBtn.classList.add('hidden'); // Hide view button
1228
+ }
1229
+ };
1230
+ // Clear dimensions/metadata if the image fails to load
1231
+ currentImage.onerror = () => {
1232
+ mediaDimensionsDisplay.textContent = 'Dimensions: Error';
1233
+ mediaMetadataDisplay.textContent = '';
1234
+ viewMetadataBtn.classList.add('hidden'); // Hide view button
1235
+ }
1236
  };
1237
+ reader.readAsDataURL(file); // Read the file to trigger onload/onerror
1238
  } else if (file.type.startsWith('video/')) {
1239
  // Display video
1240
  const reader = new FileReader();
 
1247
  currentVideo.volume = volumeSlider.value;
1248
  playIcon.className = 'fas fa-play';
1249
  playIconBottom.className = 'fas fa-play';
1250
+ mediaDimensionsDisplay.textContent = '';
1251
+ mediaMetadataDisplay.textContent = '';
1252
+ viewMetadataBtn.classList.add('hidden'); // Hide view button
1253
  };
1254
  reader.readAsDataURL(file);
1255
  }
 
1312
 
1313
  function goToPrevious() {
1314
  if (mediaFiles.length === 0) return;
1315
+
1316
+ let prevIndex = currentIndex;
1317
+ let attempts = 0;
1318
+ const maxAttempts = mediaFiles.length;
1319
+
1320
+ do {
1321
+ prevIndex = (prevIndex - 1 + mediaFiles.length) % mediaFiles.length;
1322
+ attempts++;
1323
+ // Stop if we've looped through all files or found an image (or if not skipping videos)
1324
+ if (attempts >= maxAttempts || !isSlideshowRunning || !skipVideosInSlideshow || mediaFiles[prevIndex].type.startsWith('image/')) {
1325
+ break;
1326
+ }
1327
+ } while (true);
1328
+
1329
+ // Only update if we found a valid index (prevents infinite loop if only videos exist and skipping is on)
1330
+ if (attempts < maxAttempts || !mediaFiles[prevIndex].type.startsWith('video/')) {
1331
+ currentIndex = prevIndex;
1332
+ displayCurrentMedia();
1333
+ } else if (isSlideshowRunning) {
1334
+ // If only videos exist and we're skipping, stop the slideshow
1335
+ stopSlideshow();
1336
+ showStatusMessage("Slideshow stopped: No images found to display.");
1337
  }
1338
  }
1339
 
1340
  function goToNext() {
1341
  if (mediaFiles.length === 0) return;
1342
+
1343
+ let nextIndex = currentIndex;
1344
+ let attempts = 0;
1345
+ const maxAttempts = mediaFiles.length;
1346
+
1347
+ do {
1348
+ nextIndex = (nextIndex + 1) % mediaFiles.length;
1349
+ attempts++;
1350
+ // Stop if we've looped through all files or found an image (or if not skipping videos)
1351
+ if (attempts >= maxAttempts || !isSlideshowRunning || !skipVideosInSlideshow || mediaFiles[nextIndex].type.startsWith('image/')) {
1352
+ break;
1353
+ }
1354
+ } while (true);
1355
+
1356
+ // Only update if we found a valid index (prevents infinite loop if only videos exist and skipping is on)
1357
+ if (attempts < maxAttempts || !mediaFiles[nextIndex].type.startsWith('video/')) {
1358
+ currentIndex = nextIndex;
1359
+ displayCurrentMedia();
1360
+ } else if (isSlideshowRunning) {
1361
+ // If only videos exist and we're skipping, stop the slideshow
1362
+ stopSlideshow();
1363
+ showStatusMessage("Slideshow stopped: No images found to display.");
1364
+ }
1365
  }
1366
 
1367
  function zoomImage(factor) {
 
1416
  }
1417
 
1418
  function togglePlayPause() {
1419
+ const currentFile = mediaFiles[currentIndex];
1420
+
1421
+ // If it's an image, toggle slideshow
1422
+ if (currentFile?.type.startsWith('image/')) {
1423
  if (!isSlideshowRunning) {
1424
+ startSlideshow(); // Explicitly start
1425
  } else {
1426
  stopSlideshow();
1427
  }
1428
  return;
1429
  }
1430
+
1431
+ // If it's a video, toggle play/pause
1432
+ if (currentFile?.type.startsWith('video/')) {
1433
+ // Check if video source is actually loaded before trying to play
1434
+ if (currentVideo.readyState >= 1) { // HAVE_METADATA or higher
1435
+ if (currentVideo.paused) {
1436
+ currentVideo.play().catch(e => console.error("Video play error:", e)); // Add catch for potential errors
1437
+ playIcon.className = 'fas fa-pause';
1438
+ playIconBottom.className = 'fas fa-pause';
1439
+ } else {
1440
+ currentVideo.pause();
1441
+ playIcon.className = 'fas fa-play';
1442
+ playIconBottom.className = 'fas fa-play';
1443
+ }
1444
+ } else {
1445
+ console.warn("Video not ready to play.");
1446
+ // Optionally show a status message to the user
1447
+ }
1448
  }
1449
  }
1450
 
 
1462
  }
1463
 
1464
  function updateVideoProgress() {
1465
+ // Only update if not currently scrubbing
1466
+ if (!isScrubbingVideo && currentVideo.duration) {
1467
+ const percent = (currentVideo.currentTime / currentVideo.duration) * 100;
1468
+ progressFill.style.width = `${percent}%`;
1469
+ }
1470
  }
1471
 
1472
  function toggleSlideshow() {
 
1583
  currentVideo.volume = Math.max(0, currentVideo.volume - 0.1);
1584
  volumeSlider.value = currentVideo.volume;
1585
  }
1586
+ break; // Fixed missing break
1587
  case 't':
1588
  case 'T':
1589
  toggleThumbnailSidebar();
1590
  break;
1591
+ case 'c':
1592
+ case 'C':
1593
+ copyImageToClipboard();
1594
+ break;
1595
+ case 'f':
1596
+ case 'F':
1597
+ toggleFullScreen();
1598
+ break;
1599
  case 's':
1600
  case 'S':
1601
  toggleSlideshow();
1602
  break;
1603
  case 'Escape':
1604
+ if (metadataPanel.classList.contains('active')) {
1605
+ hideMetadataPanel();
1606
+ } else {
1607
+ closeSettings();
1608
+ }
1609
  break;
1610
  }
1611
  }
 
1710
  matrixRainContainer.style.display = 'none';
1711
  }
1712
  }
1713
+
1714
+ function toggleFullScreen() {
1715
+ if (!document.fullscreenElement && // Standard syntax
1716
+ !document.mozFullScreenElement && // Firefox
1717
+ !document.webkitFullscreenElement && // Chrome, Safari and Opera
1718
+ !document.msFullscreenElement) { // IE/Edge
1719
+ if (document.documentElement.requestFullscreen) {
1720
+ document.documentElement.requestFullscreen();
1721
+ } else if (document.documentElement.mozRequestFullScreen) { /* Firefox */
1722
+ document.documentElement.mozRequestFullScreen();
1723
+ } else if (document.documentElement.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
1724
+ document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
1725
+ } else if (document.documentElement.msRequestFullscreen) { /* IE/Edge */
1726
+ document.documentElement.msRequestFullscreen();
1727
+ }
1728
+ } else {
1729
+ if (document.exitFullscreen) {
1730
+ document.exitFullscreen();
1731
+ } else if (document.mozCancelFullScreen) { /* Firefox */
1732
+ document.mozCancelFullScreen();
1733
+ } else if (document.webkitExitFullscreen) { /* Chrome, Safari and Opera */
1734
+ document.webkitExitFullscreen();
1735
+ } else if (document.msExitFullscreen) { /* IE/Edge */
1736
+ document.msExitFullscreen();
1737
+ }
1738
+ }
1739
+ }
1740
+
1741
+ function handleFullscreenChange() {
1742
+ const button = toggleFullscreenBtn; // Use the variable directly
1743
+ const icon = button.querySelector('i');
1744
+ const textSpan = button.querySelector('span.button-text'); // Target the span
1745
+
1746
+ if (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement) {
1747
+ icon.className = 'fas fa-compress mr-1';
1748
+ if (textSpan) textSpan.textContent = ' EXIT FS'; // Update span text
1749
+ button.setAttribute('data-tooltip', 'EXIT FULLSCREEN (F)');
1750
+ } else {
1751
+ icon.className = 'fas fa-expand mr-1';
1752
+ if (textSpan) textSpan.textContent = ' FULLSCREEN'; // Update span text
1753
+ button.setAttribute('data-tooltip', 'FULLSCREEN (F)');
1754
+ }
1755
+ }
1756
+
1757
+ // --- New Functions ---
1758
+
1759
+ async function copyImageToClipboard() {
1760
+ if (!mediaFiles[currentIndex]?.type.startsWith('image/')) {
1761
+ showStatusMessage('COPY FAILED: NO IMAGE LOADED');
1762
+ return;
1763
+ }
1764
+
1765
+ try {
1766
+ const response = await fetch(currentImage.src);
1767
+ const blob = await response.blob();
1768
+
1769
+ // Ensure blob type is correct, default to png if needed
1770
+ let imageType = blob.type;
1771
+ if (!imageType.startsWith('image/')) {
1772
+ console.warn('Blob type unknown, assuming image/png for clipboard');
1773
+ imageType = 'image/png';
1774
+ }
1775
+
1776
+ await navigator.clipboard.write([
1777
+ new ClipboardItem({
1778
+ [imageType]: blob
1779
+ })
1780
+ ]);
1781
+ showStatusMessage('IMAGE COPIED TO CLIPBOARD');
1782
+ } catch (err) {
1783
+ console.error('Failed to copy image:', err);
1784
+ showStatusMessage('COPY FAILED: SEE CONSOLE');
1785
+ }
1786
+ }
1787
+
1788
+ // --- Video Scrubbing Functions ---
1789
+ function startVideoScrub(e) {
1790
+ if (!mediaFiles[currentIndex]?.type.startsWith('video/')) return;
1791
+ isScrubbingVideo = true;
1792
+ updateVideoTimeFromScrub(e);
1793
+ }
1794
+
1795
+ function videoScrub(e) {
1796
+ if (!isScrubbingVideo) return;
1797
+ updateVideoTimeFromScrub(e);
1798
+ }
1799
+
1800
+ function stopVideoScrub() {
1801
+ if (isScrubbingVideo) {
1802
+ isScrubbingVideo = false;
1803
+ // Optional: Resume playback if it was playing before scrub
1804
+ // if (!currentVideo.paused) currentVideo.play();
1805
+ }
1806
+ }
1807
+
1808
+ function updateVideoTimeFromScrub(e) {
1809
+ const rect = progressBarContainer.getBoundingClientRect();
1810
+ const offsetX = e.clientX - rect.left;
1811
+ const width = rect.width;
1812
+ let percentage = offsetX / width;
1813
+ percentage = Math.max(0, Math.min(1, percentage)); // Clamp between 0 and 1
1814
+
1815
+ if (currentVideo.duration) {
1816
+ currentVideo.currentTime = percentage * currentVideo.duration;
1817
+ // Manually update progress fill during scrub
1818
+ progressFill.style.width = `${percentage * 100}%`;
1819
+ }
1820
+ }
1821
+
1822
+ // --- Thumbnail Drag Scrolling Functions ---
1823
+ function startThumbnailDrag(e) {
1824
+ isDraggingThumbnails = true;
1825
+ thumbnailDragStartX = e.pageX - thumbnailSidebar.offsetLeft;
1826
+ thumbnailScrollLeftStart = thumbnailSidebar.scrollLeft;
1827
+ thumbnailSidebar.style.cursor = 'grabbing'; // Change cursor
1828
+ thumbnailSidebar.style.userSelect = 'none'; // Prevent text selection
1829
+ }
1830
+
1831
+ function thumbnailDrag(e) {
1832
+ if (!isDraggingThumbnails) return;
1833
+ e.preventDefault(); // Prevent default drag behavior
1834
+ const x = e.pageX - thumbnailSidebar.offsetLeft;
1835
+ const walk = (x - thumbnailDragStartX) * 1.5; // Multiplier for faster scroll
1836
+ thumbnailSidebar.scrollLeft = thumbnailScrollLeftStart - walk;
1837
+ }
1838
+
1839
+ function stopThumbnailDrag() {
1840
+ if (isDraggingThumbnails) {
1841
+ isDraggingThumbnails = false;
1842
+ thumbnailSidebar.style.cursor = 'grab'; // Reset cursor
1843
+ thumbnailSidebar.style.removeProperty('user-select');
1844
+ }
1845
+ }
1846
+
1847
+ // --- Metadata Panel Functions ---
1848
+ function showMetadataPanel() {
1849
+ fullMetadataContent.textContent = fullMetadataText; // Populate with stored full text
1850
+ metadataPanel.classList.add('active');
1851
+ metadataOverlay.classList.add('active');
1852
+ }
1853
+
1854
+ function hideMetadataPanel() {
1855
+ metadataPanel.classList.remove('active');
1856
+ metadataOverlay.classList.remove('active');
1857
+ }
1858
+
1859
+ function copyFullMetadata() {
1860
+ navigator.clipboard.writeText(fullMetadataText)
1861
+ .then(() => showStatusMessage('METADATA COPIED'))
1862
+ .catch(err => {
1863
+ console.error('Failed to copy metadata:', err);
1864
+ showStatusMessage('METADATA COPY FAILED');
1865
+ });
1866
+ }
1867
+
1868
+ // --- Update Metadata Display Logic ---
1869
+ function updateMetadataDisplay(metadata) {
1870
+ fullMetadataText = Object.entries(metadata).map(([k, v]) => `${k}: ${v}`).join('\n'); // Store full text
1871
+
1872
+ let displayText = metadata['parameters'] || metadata['Comment'] || metadata['Description'] || Object.entries(metadata).slice(0, 2).map(([k, v]) => `${k}: ${v.substring(0, 50)}...`).join('; ');
1873
+ let isTruncated = false;
1874
+ if (displayText.length > 100) {
1875
+ displayText = displayText.substring(0, 97) + '...';
1876
+ isTruncated = true;
1877
+ }
1878
+ mediaMetadataDisplay.textContent = `Metadata: ${displayText}`;
1879
+ mediaMetadataDisplay.title = fullMetadataText; // Full metadata in tooltip
1880
+
1881
+ if (isTruncated || Object.keys(metadata).length > 0) { // Show button if truncated or if any metadata exists
1882
+ viewMetadataBtn.classList.remove('hidden');
1883
+ } else {
1884
+ viewMetadataBtn.classList.add('hidden');
1885
+ }
1886
+ }
1887
+
1888
+ // Modify extractAndDisplayPngMetadata to use the new update function
1889
+ async function extractAndDisplayPngMetadata(file) {
1890
+ mediaMetadataDisplay.textContent = 'Metadata: Reading...';
1891
+ mediaMetadataDisplay.title = '';
1892
+ viewMetadataBtn.classList.add('hidden'); // Hide button initially
1893
+ fullMetadataText = ''; // Clear stored text
1894
+
1895
+ try {
1896
+ const buffer = await file.arrayBuffer();
1897
+ const dataView = new DataView(buffer);
1898
+
1899
+ if (dataView.getUint32(0) !== 0x89504E47 || dataView.getUint32(4) !== 0x0D0A1A0A) {
1900
+ mediaMetadataDisplay.textContent = 'Metadata: Not a valid PNG';
1901
+ return;
1902
+ }
1903
+
1904
+ let offset = 8;
1905
+ let metadata = {};
1906
+ let foundMetadata = false;
1907
+
1908
+ while (offset < buffer.byteLength) {
1909
+ const length = dataView.getUint32(offset);
1910
+ const typeCode = dataView.getUint32(offset + 4);
1911
+ const type = String.fromCharCode(
1912
+ (typeCode >> 24) & 0xFF, (typeCode >> 16) & 0xFF, (typeCode >> 8) & 0xFF, typeCode & 0xFF
1913
+ );
1914
+
1915
+ if (type === 'tEXt') {
1916
+ const chunkDataOffset = offset + 8;
1917
+ const chunkData = new Uint8Array(buffer, chunkDataOffset, length);
1918
+ const separatorIndex = chunkData.findIndex(byte => byte === 0);
1919
+ if (separatorIndex !== -1) {
1920
+ const decoder = new TextDecoder("iso-8859-1");
1921
+ const keyword = decoder.decode(chunkData.slice(0, separatorIndex));
1922
+ const text = decoder.decode(chunkData.slice(separatorIndex + 1));
1923
+ metadata[keyword] = text;
1924
+ foundMetadata = true;
1925
+ }
1926
+ }
1927
+ // TODO: Add iTXt/zTXt parsing if needed
1928
+
1929
+ if (type === 'IEND') break;
1930
+ offset += 12 + length;
1931
+ }
1932
+
1933
+ if (foundMetadata) {
1934
+ updateMetadataDisplay(metadata); // Use the new function to display/store
1935
+ } else {
1936
+ mediaMetadataDisplay.textContent = 'Metadata: None found';
1937
+ }
1938
+
1939
+ } catch (error) {
1940
+ console.error("Error reading PNG metadata:", error);
1941
+ mediaMetadataDisplay.textContent = 'Metadata: Error reading';
1942
+ }
1943
+ }
1944
+
1945
+ }); // End of DOMContentLoaded listener
1946
  </script>
1947
+ </body>
1948
  </html>