w4nn4b3M4ST3R commited on
Commit
bc5a8d0
·
1 Parent(s): a1da080
Files changed (3) hide show
  1. app/index.html +11 -10
  2. app/static/script.js +92 -68
  3. app/static/styles.css +75 -0
app/index.html CHANGED
@@ -684,7 +684,7 @@
684
  </div>
685
 
686
  <h3 class="text-xl font-bold text-white mb-2 text-center tracking-wide uppercase">
687
- Nebula Processing
688
  </h3>
689
  <p
690
  id="loading-text"
@@ -706,15 +706,16 @@
706
  </div>
707
  </div>
708
 
709
- <div
710
- id="image-modal"
711
- class="hidden fixed inset-0 bg-black/95 z-[10000] flex items-center justify-center p-8 backdrop-blur-sm"
712
- onclick="this.classList.add('hidden')"
713
- >
714
- <img
715
- id="modal-image"
716
- class="max-w-full max-h-full object-contain rounded shadow-2xl border border-white/10"
717
- />
 
718
  </div>
719
 
720
  <div
 
684
  </div>
685
 
686
  <h3 class="text-xl font-bold text-white mb-2 text-center tracking-wide uppercase">
687
+ Processing
688
  </h3>
689
  <p
690
  id="loading-text"
 
706
  </div>
707
  </div>
708
 
709
+ <div id="image-modal" class="hidden fixed inset-0 bg-black/95 z-[10000] flex items-center justify-center p-8 backdrop-blur-sm group" onclick="this.classList.add('hidden')">
710
+ <div class="absolute inset-0 pointer-events-none z-10 bg-scanline opacity-20"></div>
711
+ <div class="absolute inset-0 pointer-events-none z-20 border-[1px] border-white/10 m-10 rounded-lg">
712
+ <div class="absolute top-0 left-0 w-4 h-4 border-t-2 border-l-2 border-violet-500"></div>
713
+ <div class="absolute top-0 right-0 w-4 h-4 border-t-2 border-r-2 border-violet-500"></div>
714
+ <div class="absolute bottom-0 left-0 w-4 h-4 border-b-2 border-l-2 border-violet-500"></div>
715
+ <div class="absolute bottom-0 right-0 w-4 h-4 border-b-2 border-r-2 border-violet-500"></div>
716
+ </div>
717
+
718
+ <img id="modal-image" class="max-w-full max-h-full object-contain rounded shadow-2xl border border-white/10 transition-transform duration-500 scale-95 opacity-0 modal-img-anim" />
719
  </div>
720
 
721
  <div
app/static/script.js CHANGED
@@ -69,19 +69,19 @@ document
69
  document
70
  .getElementById("start-clustering-btn")
71
  .addEventListener("click", async () => {
72
- if (!uploadedFiles || uploadedFiles.length === 0)
73
- return alert("Please select a folder first.");
74
  if (isConfigOpen) toggleConfigBtn.click();
75
-
 
 
 
76
  loadingOverlay.classList.remove("hidden");
 
 
77
  const hero = document.getElementById("hero-landing");
78
- if (hero) {
79
- hero.classList.add("fade-out-overlay");
80
-
81
- setTimeout(() => {
82
- hero.classList.add("hidden");
83
- }, 800);
84
- }
85
  loadingText.textContent = "Uploading images...";
86
  loadingBar.style.width = "10%";
87
 
@@ -98,7 +98,7 @@ document
98
  }
99
  }
100
 
101
- loadingText.textContent = "Analyzing (Semantic + Perceptual)...";
102
  loadingBar.style.width = "60%";
103
 
104
  try {
@@ -744,41 +744,42 @@ document.getElementById("toggle-lines").onchange = () => {
744
 
745
  // Improved tab switching with proper cleanup
746
  document.querySelectorAll(".tab-button").forEach((btn) => {
747
- btn.addEventListener("click", () => {
748
- const newTab = btn.dataset.tab;
749
- const oldTab = universeState.currentTab;
750
-
751
- // Update tab UI
752
- document
753
- .querySelectorAll(".tab-button")
754
- .forEach((b) => b.classList.remove("active"));
755
- document
756
- .querySelectorAll(".tab-content")
757
- .forEach((c) => c.classList.add("hidden"));
758
- btn.classList.add("active");
759
- document.getElementById(`tab-${newTab}`).classList.remove("hidden");
760
-
761
- // Handle universe tab specifically
762
- if (oldTab === "universe" && newTab !== "universe") {
763
- // Leaving universe tab - stop rotation
764
- stopRotation();
765
- }
766
 
767
- universeState.currentTab = newTab;
 
768
 
769
- if (newTab === "universe") {
770
- setTimeout(() => {
771
- if (universeState.isInitialized) {
772
- try {
 
 
 
 
 
 
 
 
 
 
773
  Plotly.Plots.resize("plotly-div");
774
- if (document.getElementById("toggle-rotate").checked) {
775
- startRotation();
776
- }
777
- } catch (e) {
778
- console.error("Universe resize error:", e);
779
- }
780
- }
781
- }, 100);
 
 
 
 
 
782
  }
783
  });
784
  });
@@ -801,6 +802,7 @@ function renderClusterList() {
801
 
802
  function loadCluster(name) {
803
  currentClusterName = name;
 
804
  document
805
  .querySelectorAll(".cluster-button")
806
  .forEach((b) => b.classList.remove("active"));
@@ -813,49 +815,73 @@ function loadCluster(name) {
813
  document.getElementById(
814
  "thumbnail-header"
815
  ).textContent = `Cluster Content: ${name}`;
 
816
  const q = qualityScores[name]?.images || [];
817
 
818
  document.getElementById("delete-btn").disabled = false;
819
  document.getElementById("move-btn").disabled = false;
820
  document.getElementById("smart-cleanup-btn").disabled = false;
821
 
822
- currentGroups[name].forEach((path) => {
823
  const url = `${API_URL}/results/${currentSessionId}/clusters/${path}`;
824
  const info = q.find((i) => i.path === path);
825
  const isBest = info?.is_best;
826
 
827
  const div = document.createElement("div");
 
 
 
 
828
  div.className = `thumbnail-card rounded p-2 flex flex-col relative group ${
829
  isBest ? "best-quality" : ""
830
  }`;
831
  div.dataset.path = path;
 
832
  div.innerHTML = `
833
- <input type="checkbox" class="mb-1 z-20 accent-violet-500 cursor-pointer">
834
- ${isBest ? '<div class="best-badge">BEST</div>' : ""}
835
- ${
836
- info
837
- ? `<div class="quality-badge" style="background:${
838
- info.quality_color
839
- }">${info.scores.total.toFixed(0)}
840
- <div class="quality-details"><div class="quality-metric"><span class="quality-metric-label">Res</span><span class="quality-metric-value">${
841
- info.scores.resolution
842
- }</span></div><div class="quality-metric"><span class="quality-metric-label">Sharp</span><span class="quality-metric-value">${
843
- info.scores.sharpness
844
- }</span></div></div>
845
- </div>`
846
- : ""
847
- }
848
- <div class="relative overflow-hidden rounded aspect-square bg-black">
849
- <img src="${url}" loading="lazy" class="w-full h-full object-contain transition-transform duration-300 group-hover:scale-110 cursor-zoom-in" onclick="event.stopPropagation();document.getElementById('modal-image').src='${url}';document.getElementById('image-modal').classList.remove('hidden')">
850
- </div>
851
- <div class="text-[10px] text-gray-400 truncate mt-2 font-mono text-center">${path
852
- .split("/")
853
- .pop()}</div>
854
- `;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
855
  div.querySelector("input").onclick = (e) => {
856
  e.stopPropagation();
857
  div.classList.toggle("selected", e.target.checked);
858
  };
 
859
  gallery.appendChild(div);
860
  });
861
  }
@@ -1088,14 +1114,12 @@ document.getElementById("keep-best-btn").onclick = () => {
1088
  }
1089
  };
1090
 
1091
- // Modal click outside properly
1092
  document.getElementById("image-modal").onclick = (e) => {
1093
  if (e.target.id === "image-modal") {
1094
  e.target.classList.add("hidden");
1095
  }
1096
  };
1097
 
1098
- // Cleanup on page unload
1099
  window.addEventListener("beforeunload", () => {
1100
  stopRotation();
1101
  Object.values(chartInstances).forEach((chart) => {
 
69
  document
70
  .getElementById("start-clustering-btn")
71
  .addEventListener("click", async () => {
72
+ if (!uploadedFiles || uploadedFiles.length === 0) return alert("Please select a folder first.");
73
+
74
  if (isConfigOpen) toggleConfigBtn.click();
75
+ document.body.classList.add("warp-active");
76
+
77
+ await new Promise(r => setTimeout(r, 800));
78
+
79
  loadingOverlay.classList.remove("hidden");
80
+ document.body.classList.remove("warp-active");
81
+
82
  const hero = document.getElementById("hero-landing");
83
+ if (hero) hero.classList.add("hidden");
84
+
 
 
 
 
 
85
  loadingText.textContent = "Uploading images...";
86
  loadingBar.style.width = "10%";
87
 
 
98
  }
99
  }
100
 
101
+ loadingText.textContent = "This may takes a few minutes...";
102
  loadingBar.style.width = "60%";
103
 
104
  try {
 
744
 
745
  // Improved tab switching with proper cleanup
746
  document.querySelectorAll(".tab-button").forEach((btn) => {
747
+ btn.addEventListener("click", () => {
748
+ const newTabId = btn.dataset.tab;
749
+ const currentActiveContent = document.querySelector(".tab-content.fade-in");
750
+ const newContent = document.getElementById(`tab-${newTabId}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
 
752
+ document.querySelectorAll(".tab-button").forEach((b) => b.classList.remove("active"));
753
+ btn.classList.add("active");
754
 
755
+ const showNewTab = () => {
756
+ document.querySelectorAll(".tab-content").forEach(c => {
757
+ c.classList.remove("active-tab", "fade-in");
758
+ c.style.display = "none";
759
+ });
760
+
761
+ newContent.style.display = "block";
762
+ requestAnimationFrame(() => {
763
+ newContent.classList.add("active-tab", "fade-in");
764
+ });
765
+
766
+ if (newTabId === "universe") {
767
+ universeState.currentTab = "universe";
768
+ setTimeout(() => {
769
  Plotly.Plots.resize("plotly-div");
770
+ if (document.getElementById("toggle-rotate").checked) startRotation();
771
+ }, 350);
772
+ } else {
773
+ stopRotation();
774
+ universeState.currentTab = newTabId;
775
+ }
776
+ };
777
+
778
+ if (currentActiveContent && currentActiveContent !== newContent) {
779
+ currentActiveContent.classList.remove("fade-in");
780
+ setTimeout(showNewTab, 300);
781
+ } else {
782
+ showNewTab();
783
  }
784
  });
785
  });
 
802
 
803
  function loadCluster(name) {
804
  currentClusterName = name;
805
+
806
  document
807
  .querySelectorAll(".cluster-button")
808
  .forEach((b) => b.classList.remove("active"));
 
815
  document.getElementById(
816
  "thumbnail-header"
817
  ).textContent = `Cluster Content: ${name}`;
818
+
819
  const q = qualityScores[name]?.images || [];
820
 
821
  document.getElementById("delete-btn").disabled = false;
822
  document.getElementById("move-btn").disabled = false;
823
  document.getElementById("smart-cleanup-btn").disabled = false;
824
 
825
+ currentGroups[name].forEach((path, index) => {
826
  const url = `${API_URL}/results/${currentSessionId}/clusters/${path}`;
827
  const info = q.find((i) => i.path === path);
828
  const isBest = info?.is_best;
829
 
830
  const div = document.createElement("div");
831
+
832
+ const delay = Math.min(index * 30, 1000);
833
+ div.style.animationDelay = `${delay}ms`;
834
+
835
  div.className = `thumbnail-card rounded p-2 flex flex-col relative group ${
836
  isBest ? "best-quality" : ""
837
  }`;
838
  div.dataset.path = path;
839
+
840
  div.innerHTML = `
841
+ <input type="checkbox" class="mb-1 z-20 accent-violet-500 cursor-pointer">
842
+ ${isBest ? '<div class="best-badge">BEST</div>' : ""}
843
+ ${
844
+ info
845
+ ? `<div class="quality-badge" style="background:${
846
+ info.quality_color
847
+ }">${info.scores.total.toFixed(0)}
848
+ <div class="quality-details"><div class="quality-metric"><span class="quality-metric-label">Res</span><span class="quality-metric-value">${
849
+ info.scores.resolution
850
+ }</span></div><div class="quality-metric"><span class="quality-metric-label">Sharp</span><span class="quality-metric-value">${
851
+ info.scores.sharpness
852
+ }</span></div></div>
853
+ </div>`
854
+ : ""
855
+ }
856
+ <div class="relative overflow-hidden rounded aspect-square bg-black">
857
+ <img src="${url}" loading="lazy" class="w-full h-full object-contain transition-transform duration-300 group-hover:scale-110 cursor-zoom-in">
858
+ </div>
859
+ <div class="text-[10px] text-gray-400 truncate mt-2 font-mono text-center">${path
860
+ .split("/")
861
+ .pop()}</div>
862
+ `;
863
+
864
+ const img = div.querySelector('img');
865
+ img.onclick = (e) => {
866
+ e.stopPropagation();
867
+ const modal = document.getElementById('image-modal');
868
+ const modalImg = document.getElementById('modal-image');
869
+
870
+ modalImg.src = url;
871
+
872
+ modalImg.classList.remove('show');
873
+ modal.classList.remove('hidden');
874
+
875
+ setTimeout(() => {
876
+ modalImg.classList.add('show');
877
+ }, 50);
878
+ };
879
+
880
  div.querySelector("input").onclick = (e) => {
881
  e.stopPropagation();
882
  div.classList.toggle("selected", e.target.checked);
883
  };
884
+
885
  gallery.appendChild(div);
886
  });
887
  }
 
1114
  }
1115
  };
1116
 
 
1117
  document.getElementById("image-modal").onclick = (e) => {
1118
  if (e.target.id === "image-modal") {
1119
  e.target.classList.add("hidden");
1120
  }
1121
  };
1122
 
 
1123
  window.addEventListener("beforeunload", () => {
1124
  stopRotation();
1125
  Object.values(chartInstances).forEach((chart) => {
app/static/styles.css CHANGED
@@ -659,4 +659,79 @@ body {
659
 
660
  .text-glow-anim {
661
  animation: text-shimmer 4s infinite alternate;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
662
  }
 
659
 
660
  .text-glow-anim {
661
  animation: text-shimmer 4s infinite alternate;
662
+ }
663
+
664
+ .warp-overlay {
665
+ position: fixed;
666
+ inset: 0;
667
+ background: black;
668
+ z-index: 9000;
669
+ opacity: 0;
670
+ pointer-events: none;
671
+ transition: opacity 0.5s;
672
+ }
673
+
674
+ .warp-active .star-layer {
675
+ /* Kéo dài các ngôi sao thành vạch sáng */
676
+ transform-origin: center center;
677
+ transition: transform 0.8s cubic-bezier(0.7, 0, 0.3, 1), opacity 0.5s;
678
+ transform: scale(5) rotate(10deg);
679
+ opacity: 0;
680
+ filter: blur(2px);
681
+ }
682
+
683
+ .warp-active .hero-content-wrapper {
684
+ /* Nội dung chính phóng to và biến mất */
685
+ animation: hyperspace-jump 0.8s cubic-bezier(0.7, 0, 0.3, 1) forwards;
686
+ }
687
+
688
+ @keyframes hyperspace-jump {
689
+ 0% { transform: scale(1); opacity: 1; filter: blur(0); }
690
+ 40% { opacity: 0.8; filter: blur(2px); }
691
+ 100% { transform: scale(4); opacity: 0; filter: blur(20px); }
692
+ }
693
+
694
+ @keyframes sci-fi-reveal {
695
+ 0% {
696
+ opacity: 0;
697
+ transform: translateY(20px) scale(0.9);
698
+ filter: brightness(2) blur(4px);
699
+ }
700
+ 100% {
701
+ opacity: 1;
702
+ transform: translateY(0) scale(1);
703
+ filter: brightness(1) blur(0);
704
+ }
705
+ }
706
+
707
+ .thumbnail-card {
708
+ opacity: 0;
709
+ animation: sci-fi-reveal 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
710
+ }
711
+
712
+ .tab-content {
713
+ transition: opacity 0.3s ease-in-out, transform 0.3s ease-out;
714
+ opacity: 0;
715
+ transform: scale(0.98);
716
+ display: none;
717
+ }
718
+
719
+ .tab-content.active-tab {
720
+ display: block;
721
+ }
722
+
723
+ .tab-content.fade-in {
724
+ opacity: 1;
725
+ transform: scale(1);
726
+ }
727
+
728
+ .bg-scanline {
729
+ background: linear-gradient(to bottom, transparent 50%, rgba(0, 0, 0, 0.3) 51%);
730
+ background-size: 100% 4px;
731
+ }
732
+
733
+ .modal-img-anim.show {
734
+ transform: scale(1);
735
+ opacity: 1;
736
+ box-shadow: 0 0 50px rgba(139, 92, 246, 0.2);
737
  }