diff --git a/src/static/script.js b/src/JavaScript/script.js
similarity index 68%
rename from src/static/script.js
rename to src/JavaScript/script.js
index 47e7c17..104fab4 100644
--- a/src/static/script.js
+++ b/src/JavaScript/script.js
@@ -1,692 +1,717 @@
-document.addEventListener("DOMContentLoaded", () => {
- "use strict";
-
- // Utility functions
- const utils = {
- safeLocalStorage: {
- getItem(key) {
- try {
- return localStorage.getItem(key);
- } catch (e) {
- console.warn("localStorage.getItem failed:", e);
- return null;
- }
- },
- setItem(key, value) {
- try {
- localStorage.setItem(key, value);
- return true;
- } catch (e) {
- console.warn("localStorage.setItem failed:", e);
- return false;
- }
- }
- },
-
- debounce(func, wait) {
- let timeout;
- return function executedFunction(...args) {
- const later = () => {
- clearTimeout(timeout);
- func.apply(this, args);
- };
- clearTimeout(timeout);
- timeout = setTimeout(later, wait);
- };
- },
-
- throttle(func, limit) {
- let inThrottle;
- return function executedFunction(...args) {
- if (!inThrottle) {
- func.apply(this, args);
- inThrottle = true;
- setTimeout(() => inThrottle = false, limit);
- }
- };
- }
- };
-
- // Dark Mode Manager
- class DarkModeManager {
- constructor() {
- this.darkModeToggle = document.getElementById("darkModeToggle");
- this.html = document.documentElement;
- this.init();
- }
-
- init() {
- this.loadSavedPreference();
- this.attachEventListeners();
- }
-
- loadSavedPreference() {
- const savedDarkMode = utils.safeLocalStorage.getItem("darkMode");
- if (
- savedDarkMode === "true" ||
- (savedDarkMode === null &&
- window.matchMedia("(prefers-color-scheme: dark)").matches)
- ) {
- this.html.classList.add("dark");
- }
- }
-
- attachEventListeners() {
- this.darkModeToggle?.addEventListener("click", () => {
- this.html.classList.toggle("dark");
- utils.safeLocalStorage.setItem("darkMode", this.html.classList.contains("dark"));
- });
- }
- }
-
- // Problem Manager
- class ProblemManager {
- constructor() {
- this.problemSearch = document.getElementById("problemSearch");
- this.problemsContainer = document.getElementById("problemsContainer");
- this.problemsPagination = document.getElementById("problemsPagination");
- this.problemsPrevBtn = document.getElementById("problemsPrevBtn");
- this.problemsNextBtn = document.getElementById("problemsNextBtn");
- this.problemsPaginationInfo = document.getElementById("problemsPaginationInfo");
- this.difficultyFilter = document.getElementById("difficultyFilter");
- this.sortProblems = document.getElementById("sortProblems");
-
- this.allProblemItems = [];
- this.filteredProblemItems = [];
- this.currentPage = 1;
- this.itemsPerPage = 5;
- this.problemSort = { column: "alpha", direction: "asc" };
- this.problemDescriptionPopover = null;
- this.manifestCache = new Map();
-
- this.init();
- }
-
- init() {
- if (!this.problemsContainer) return;
-
- this.initializeProblemItems();
- this.attachEventListeners();
- this.injectPopoverCSS();
- this.attachProblemHoverEvents();
- }
-
- initializeProblemItems() {
- this.allProblemItems = Array.from(
- this.problemsContainer.querySelectorAll(".problem-item") || []
- );
- this.filteredProblemItems = this.allProblemItems.map(this.getProblemData);
- this.updatePagination();
- }
-
- getProblemData = (item) => ({
- element: item,
- name: item.dataset.name?.toLowerCase() || "",
- desc: item.dataset.desc?.toLowerCase() || "",
- difficulty: item.dataset.difficulty || "",
- });
-
- updatePagination() {
- const totalPages = Math.ceil(this.filteredProblemItems.length / this.itemsPerPage);
- const startIndex = (this.currentPage - 1) * this.itemsPerPage;
- const endIndex = startIndex + this.itemsPerPage;
-
- // Hide all items first
- this.allProblemItems.forEach((item) => {
- item.style.display = "none";
- });
-
- // Show current page items
- this.filteredProblemItems.slice(startIndex, endIndex).forEach((item) => {
- item.element.style.display = "";
- });
-
- // Update pagination controls
- if (this.problemsPrevBtn) this.problemsPrevBtn.disabled = this.currentPage <= 1;
- if (this.problemsNextBtn) this.problemsNextBtn.disabled = this.currentPage >= totalPages;
-
- if (this.problemsPaginationInfo) {
- this.problemsPaginationInfo.textContent =
- totalPages > 0
- ? `Page ${this.currentPage} of ${totalPages}`
- : "No problems found";
- }
-
- this.setupPaginationLayout();
- }
-
- setupPaginationLayout() {
- if (this.problemsPagination) {
- Object.assign(this.problemsPagination.style, {
- display: "flex",
- justifyContent: "center",
- position: "absolute",
- left: "0",
- right: "0",
- bottom: "0",
- margin: "0 auto",
- width: "100%",
- background: "inherit",
- borderTop: "1px solid var(--border)",
- padding: "12px 0"
- });
- this.problemsPagination.classList.remove("hidden");
- }
-
- if (this.problemsContainer?.parentElement) {
- Object.assign(this.problemsContainer.parentElement.style, {
- position: "relative",
- paddingBottom: "56px"
- });
- }
- }
-
- showProblemDescription = async (item) => {
- this.hideProblemDescription();
-
- const folder = item.querySelector('a')?.getAttribute('href')?.split('/').pop();
- if (!folder) return;
-
- try {
- let manifest = this.manifestCache.get(folder);
-
- if (!manifest) {
- // Try localStorage cache first
- const cacheKey = `problem_manifest_${folder}`;
- const cached = utils.safeLocalStorage.getItem(cacheKey);
-
- if (cached) {
- manifest = JSON.parse(cached);
- this.manifestCache.set(folder, manifest);
- } else {
- // Fetch from API
- const response = await fetch(`/api/problem_manifest/${encodeURIComponent(folder)}`);
- manifest = response.ok ? await response.json() : { description: 'No description.' };
-
- this.manifestCache.set(folder, manifest);
- utils.safeLocalStorage.setItem(cacheKey, JSON.stringify(manifest));
- }
- }
-
- this.createPopover(manifest.description || 'No description.', item);
- } catch (error) {
- console.warn("Failed to load problem description:", error);
- this.createPopover('No description available.', item);
- }
- };
-
- createPopover(description, item) {
- this.problemDescriptionPopover = document.createElement("div");
- this.problemDescriptionPopover.className = "problem-desc-popover";
- this.problemDescriptionPopover.textContent = description;
- document.body.appendChild(this.problemDescriptionPopover);
-
- const rect = item.getBoundingClientRect();
- Object.assign(this.problemDescriptionPopover.style, {
- position: "fixed",
- left: `${rect.left + window.scrollX}px`,
- top: `${rect.bottom + window.scrollY + 6}px`,
- zIndex: "1000",
- minWidth: `${rect.width}px`
- });
- }
-
- hideProblemDescription = () => {
- if (this.problemDescriptionPopover) {
- this.problemDescriptionPopover.remove();
- this.problemDescriptionPopover = null;
- }
- };
-
- attachProblemHoverEvents() {
- this.allProblemItems.forEach((item) => {
- item.addEventListener("mouseenter", () => this.showProblemDescription(item));
- item.addEventListener("mouseleave", this.hideProblemDescription);
- item.addEventListener("mousemove", this.handleMouseMove);
- });
- }
-
- handleMouseMove = utils.throttle((e) => {
- if (this.problemDescriptionPopover) {
- this.problemDescriptionPopover.style.left = `${e.clientX + 10}px`;
- }
- }, 16); // ~60fps
-
- sortProblemItems(column, direction) {
- this.filteredProblemItems.sort((a, b) => {
- let valueA, valueB;
-
- switch (column) {
- case "alpha":
- valueA = a.name;
- valueB = b.name;
- break;
- case "difficulty":
- const difficultyOrder = { easy: 1, medium: 2, hard: 3 };
- valueA = difficultyOrder[a.difficulty] || 0;
- valueB = difficultyOrder[b.difficulty] || 0;
- break;
- default:
- return 0;
- }
-
- let comparison = 0;
- if (typeof valueA === "number" && typeof valueB === "number") {
- comparison = valueA - valueB;
- } else {
- comparison = valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
- }
-
- return direction === "asc" ? comparison : -comparison;
- });
- }
-
- attachEventListeners() {
- this.problemsPrevBtn?.addEventListener("click", () => {
- if (this.currentPage > 1) {
- this.currentPage--;
- this.updatePagination();
- }
- });
-
- this.problemsNextBtn?.addEventListener("click", () => {
- const totalPages = Math.ceil(this.filteredProblemItems.length / this.itemsPerPage);
- if (this.currentPage < totalPages) {
- this.currentPage++;
- this.updatePagination();
- }
- });
-
- this.problemSearch?.addEventListener("input", utils.debounce(() => {
- this.filterProblems();
- this.currentPage = 1;
- this.updatePagination();
- }, 300));
-
- this.difficultyFilter?.addEventListener("change", () => {
- this.filterProblems();
- this.currentPage = 1;
- this.updatePagination();
- });
-
- this.sortProblems?.addEventListener("change", () => {
- const value = this.sortProblems.value;
- if (value === "alpha" || value === "difficulty") {
- if (this.problemSort.column === value) {
- this.problemSort.direction = this.problemSort.direction === "asc" ? "desc" : "asc";
- } else {
- this.problemSort.column = value;
- this.problemSort.direction = "asc";
- }
- this.sortProblemItems(this.problemSort.column, this.problemSort.direction);
- this.currentPage = 1;
- this.updatePagination();
- }
- });
- }
-
- filterProblems() {
- const searchTerm = (this.problemSearch?.value || "").toLowerCase().trim();
- const difficulty = this.difficultyFilter?.value || "all";
-
- this.filteredProblemItems = this.allProblemItems
- .map(this.getProblemData)
- .filter(item => {
- const matchesSearch = !searchTerm ||
- item.name.includes(searchTerm) ||
- item.desc.includes(searchTerm);
-
- const matchesDifficulty = difficulty === "all" ||
- item.difficulty === difficulty;
-
- return matchesSearch && matchesDifficulty;
- });
- }
-
- injectPopoverCSS() {
- if (document.getElementById("problem-desc-popover-style")) return;
-
- const style = document.createElement("style");
- style.id = "problem-desc-popover-style";
- style.textContent = `
- .problem-desc-popover {
- background: var(--card, #fff);
- color: var(--text, #222);
- border: 1px solid var(--border, #e5e7eb);
- border-radius: 8px;
- box-shadow: 0 4px 16px rgba(16,24,40,0.13);
- padding: 12px 16px;
- font-size: 0.98rem;
- max-width: 350px;
- min-width: 180px;
- pointer-events: none;
- opacity: 0.97;
- transition: opacity 0.2s;
- word-break: break-word;
- }
- html.dark .problem-desc-popover {
- background: var(--card, #1e293b);
- color: var(--text, #f1f5f9);
- border: 1px solid var(--border, #334155);
- }
- `;
- document.head.appendChild(style);
- }
-
- destroy() {
- // Clean up event listeners and resources
- this.hideProblemDescription();
- this.manifestCache.clear();
- }
- }
-
- // Leaderboard Manager
- class LeaderboardManager {
- constructor() {
- this.problemFilter = document.getElementById("problemFilter");
- this.runtimeFilter = document.getElementById("runtimeFilter");
- this.leaderboardBody = document.getElementById("leaderboardBody");
- this.sortableHeaders = document.querySelectorAll(".sortable");
- this.rankInfoBtn = document.getElementById("rankInfoBtn");
- this.rankingExplanation = document.getElementById("rankingExplanation");
-
- this.currentSort = { column: "rank", direction: "asc" };
- this.allRows = [];
-
- this.init();
- }
-
- init() {
- if (!this.leaderboardBody || this.leaderboardBody.children.length === 0) return;
-
- this.initializeRows();
- this.attachEventListeners();
- this.calculateOverallRanking();
- this.setInitialSortIndicator();
- }
-
- initializeRows() {
- this.allRows = Array.from(this.leaderboardBody.querySelectorAll("tr")).map((row, index) => ({
- element: row,
- user: row.dataset.user || "",
- problem: row.dataset.problem || "",
- runtime: parseFloat(row.dataset.runtime) || 0,
- memory: parseFloat(row.dataset.memory) || 0,
- timestamp: new Date(row.dataset.timestamp || Date.now()).getTime(),
- language: row.dataset.language || "",
- originalIndex: index,
- }));
- }
-
- updateRankClasses() {
- const visibleRows = this.allRows.filter(
- (row) => row.element.style.display !== "none"
- );
-
- visibleRows.forEach((rowData, index) => {
- const rank = index + 1;
- const row = rowData.element;
-
- // Update rank cell
- const rankCell = row.cells[0];
- if (rankCell) rankCell.textContent = rank;
-
- // Update rank classes
- row.className = row.className.replace(/\brank-\d+\b/g, "");
- if (rank === 1) row.classList.add("rank-1");
- else if (rank <= 3) row.classList.add("rank-top3");
- });
- }
-
- calculateOverallRanking() {
- const visibleRows = this.allRows.filter(
- (row) => row.element.style.display !== "none"
- );
-
- if (visibleRows.length === 0) return;
-
- // Group submissions by problem to find the best performance for each
- const problemBests = {};
-
- visibleRows.forEach((rowData) => {
- const problem = rowData.problem;
- if (!problemBests[problem]) {
- problemBests[problem] = {
- bestRuntime: Infinity,
- bestMemory: Infinity,
- };
- }
-
- problemBests[problem].bestRuntime = Math.min(
- problemBests[problem].bestRuntime,
- rowData.runtime
- );
- problemBests[problem].bestMemory = Math.min(
- problemBests[problem].bestMemory,
- rowData.memory
- );
- });
-
- // Calculate normalized scores for each submission
- visibleRows.forEach((rowData) => {
- const problemBest = problemBests[rowData.problem];
-
- const runtimeScore = problemBest.bestRuntime > 0
- ? rowData.runtime / problemBest.bestRuntime
- : 1;
- const memoryScore = problemBest.bestMemory > 0
- ? rowData.memory / problemBest.bestMemory
- : 1;
-
- // Weighted overall score (70% runtime, 30% memory)
- rowData.overallScore = runtimeScore * 0.7 + memoryScore * 0.3;
- });
-
- // Sort by overall score (lower is better), then by timestamp (earlier is better for ties)
- visibleRows.sort((a, b) => {
- const scoreDiff = a.overallScore - b.overallScore;
- if (Math.abs(scoreDiff) > 0.000001) return scoreDiff;
- return a.timestamp - b.timestamp;
- });
-
- // Reorder DOM elements and update ranks
- const fragment = document.createDocumentFragment();
- visibleRows.forEach((rowData) => {
- fragment.appendChild(rowData.element);
- });
- this.leaderboardBody.appendChild(fragment);
-
- this.updateRankClasses();
- }
-
- filterLeaderboard() {
- const problemTerm = (this.problemFilter?.value || "").toLowerCase().trim();
- const runtimeType = this.runtimeFilter?.value || "all";
-
- // Reset all rows to visible first
- this.allRows.forEach((rowData) => {
- rowData.element.style.display = "";
- });
-
- // Apply problem filter
- if (problemTerm) {
- this.allRows.forEach((rowData) => {
- const problemMatch = rowData.problem.toLowerCase().includes(problemTerm);
- if (!problemMatch) {
- rowData.element.style.display = "none";
- }
- });
- }
-
- // Apply runtime filter (best/worst per user per problem)
- if (runtimeType === "best" || runtimeType === "worst") {
- const userProblemGroups = {};
-
- // Group by user + problem combination
- this.allRows.forEach((rowData) => {
- if (rowData.element.style.display === "none") return;
-
- const key = `${rowData.user}::${rowData.problem}`;
- if (!userProblemGroups[key]) {
- userProblemGroups[key] = [];
- }
- userProblemGroups[key].push(rowData);
- });
-
- // Hide all except best/worst for each user-problem combination
- Object.values(userProblemGroups).forEach((group) => {
- if (group.length <= 1) return;
-
- group.sort((a, b) => a.runtime - b.runtime);
-
- const keepIndex = runtimeType === "best" ? 0 : group.length - 1;
- group.forEach((rowData, index) => {
- if (index !== keepIndex) {
- rowData.element.style.display = "none";
- }
- });
- });
- }
-
- this.calculateOverallRanking();
- }
-
- getCellValue(rowData, column) {
- switch (column) {
- case "rank":
- return parseInt(rowData.element.cells[0]?.textContent) || 0;
- case "user":
- return rowData.user.toLowerCase();
- case "problem":
- return rowData.problem.toLowerCase();
- case "runtime":
- return rowData.runtime;
- case "memory":
- return rowData.memory;
- case "timestamp":
- return rowData.timestamp;
- case "language":
- return rowData.language.toLowerCase();
- default:
- return "";
- }
- }
-
- sortLeaderboard(column, direction) {
- if (column === "rank") {
- this.calculateOverallRanking();
- return;
- }
-
- const visibleRows = this.allRows.filter(
- (row) => row.element.style.display !== "none"
- );
-
- visibleRows.sort((a, b) => {
- const valueA = this.getCellValue(a, column);
- const valueB = this.getCellValue(b, column);
-
- let comparison = 0;
- if (typeof valueA === "number" && typeof valueB === "number") {
- comparison = valueA - valueB;
- } else {
- comparison = valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
- }
-
- return direction === "asc" ? comparison : -comparison;
- });
-
- // Reorder DOM elements using document fragment for better performance
- const fragment = document.createDocumentFragment();
- visibleRows.forEach((rowData) => {
- fragment.appendChild(rowData.element);
- });
- this.leaderboardBody.appendChild(fragment);
-
- this.updateRankClasses();
- }
-
- attachEventListeners() {
- // Sorting event listeners
- this.sortableHeaders.forEach((header) => {
- header.addEventListener("click", () => {
- const column = header.dataset.sort;
- if (!column) return;
-
- // Remove sorting classes from all headers
- this.sortableHeaders.forEach((h) =>
- h.classList.remove("sort-asc", "sort-desc")
- );
-
- // Toggle sort direction
- if (this.currentSort.column === column) {
- this.currentSort.direction = this.currentSort.direction === "asc" ? "desc" : "asc";
- } else {
- this.currentSort.column = column;
- this.currentSort.direction = "asc";
- }
-
- // Add sorting class to current header
- header.classList.add(`sort-${this.currentSort.direction}`);
-
- this.sortLeaderboard(column, this.currentSort.direction);
- });
- });
-
- // Filter event listeners with debouncing
- this.problemFilter?.addEventListener("input",
- utils.debounce(() => this.filterLeaderboard(), 300)
- );
- this.runtimeFilter?.addEventListener("change", () => this.filterLeaderboard());
-
- // Rank info popout
- this.rankInfoBtn?.addEventListener("click", (e) => {
- e.preventDefault();
- this.rankingExplanation?.classList.toggle("active");
- this.rankInfoBtn?.classList.toggle("active");
- });
-
- // Close ranking explanation when clicking outside
- document.addEventListener("click", (e) => {
- if (
- this.rankingExplanation?.classList.contains("active") &&
- !this.rankingExplanation.contains(e.target) &&
- !this.rankInfoBtn?.contains(e.target)
- ) {
- this.rankingExplanation.classList.remove("active");
- this.rankInfoBtn?.classList.remove("active");
- }
- });
- }
-
- setInitialSortIndicator() {
- const defaultHeader = document.querySelector('[data-sort="rank"]');
- if (defaultHeader) {
- defaultHeader.classList.add("sort-asc");
- }
- }
- }
-
- // Initialize all managers
- const darkModeManager = new DarkModeManager();
- const problemManager = new ProblemManager();
- const leaderboardManager = new LeaderboardManager();
-
- // Apply dark mode to dynamically created elements
- const applyDarkModeToElements = () => {
- // Any additional dark mode styling for dynamically created elements can go here
- };
-
- // Watch for dark mode changes
- const darkModeObserver = new MutationObserver(applyDarkModeToElements);
- darkModeObserver.observe(document.documentElement, {
- attributes: true,
- attributeFilter: ["class"],
- });
-
- // Cleanup on page unload
- window.addEventListener("beforeunload", () => {
- problemManager.destroy();
- darkModeObserver.disconnect();
- });
+/**
+ *
+ * This is the stupid fucking JavaScript, i hate this so fucking much
+ * why the fuck does this need to exsits, idk.
+ *
+ * CHANGELOG:
+ * aug18@21:51-> pagination for leaderboard ; and some shit refactoring.
+ */
+document.addEventListener("DOMContentLoaded", () => {
+ "use strict";
+
+ // Utility functions
+ const utils = {
+ safeLocalStorage: {
+ getItem(key) {
+ try {
+ return localStorage.getItem(key);
+ } catch (e) {
+ console.warn("localStorage.getItem failed:", e);
+ return null;
+ }
+ },
+ setItem(key, value) {
+ try {
+ localStorage.setItem(key, value);
+ return true;
+ } catch (e) {
+ console.warn("localStorage.setItem failed:", e);
+ return false;
+ }
+ }
+ },
+
+ debounce(func, wait) {
+ let timeout;
+ return function executedFunction(...args) {
+ const later = () => {
+ clearTimeout(timeout);
+ func.apply(this, args);
+ };
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ };
+ },
+
+ throttle(func, limit) {
+ let inThrottle;
+ return function executedFunction(...args) {
+ if (!inThrottle) {
+ func.apply(this, args);
+ inThrottle = true;
+ setTimeout(() => inThrottle = false, limit);
+ }
+ };
+ }
+ };
+
+ // Dark Mode Manager
+ class DarkModeManager {
+ constructor() {
+ this.darkModeToggle = document.getElementById("darkModeToggle");
+ this.html = document.documentElement;
+ this.init();
+ }
+
+ init() {
+ this.loadSavedPreference();
+ this.attachEventListeners();
+ }
+
+ loadSavedPreference() {
+ const savedDarkMode = utils.safeLocalStorage.getItem("darkMode");
+ if (
+ savedDarkMode === "true" ||
+ (savedDarkMode === null &&
+ window.matchMedia("(prefers-color-scheme: dark)").matches)
+ ) {
+ this.html.classList.add("dark");
+ }
+ }
+
+ attachEventListeners() {
+ this.darkModeToggle?.addEventListener("click", () => {
+ this.html.classList.toggle("dark");
+ utils.safeLocalStorage.setItem("darkMode", this.html.classList.contains("dark"));
+ });
+ }
+ }
+
+ // Problem Manager
+ class ProblemManager {
+ constructor() {
+ this.problemSearch = document.getElementById("problemSearch");
+ this.problemsContainer = document.getElementById("problemsContainer");
+ this.problemsPagination = document.getElementById("problemsPagination");
+ this.problemsPrevBtn = document.getElementById("problemsPrevBtn");
+ this.problemsNextBtn = document.getElementById("problemsNextBtn");
+ this.problemsPaginationInfo = document.getElementById("problemsPaginationInfo");
+ this.difficultyFilter = document.getElementById("difficultyFilter");
+ this.sortProblems = document.getElementById("sortProblems");
+
+ this.allProblemItems = [];
+ this.filteredProblemItems = [];
+ this.currentPage = 1;
+ this.itemsPerPage = 5;
+ this.problemSort = { column: "alpha", direction: "asc" };
+ this.problemDescriptionPopover = null;
+ this.manifestCache = new Map();
+
+ this.init();
+ }
+
+ init() {
+ if (!this.problemsContainer) return;
+
+ this.initializeProblemItems();
+ this.attachEventListeners();
+ this.injectPopoverCSS();
+ this.attachProblemHoverEvents();
+ }
+
+ initializeProblemItems() {
+ this.allProblemItems = Array.from(
+ this.problemsContainer.querySelectorAll(".problem-item") || []
+ );
+ this.filteredProblemItems = this.allProblemItems.map(this.getProblemData);
+ this.updatePagination();
+ }
+
+ getProblemData = (item) => ({
+ element: item,
+ name: item.dataset.name?.toLowerCase() || "",
+ desc: item.dataset.desc?.toLowerCase() || "",
+ difficulty: item.dataset.difficulty || "",
+ });
+
+ updatePagination() {
+ const totalPages = Math.ceil(this.filteredProblemItems.length / this.itemsPerPage);
+ const startIndex = (this.currentPage - 1) * this.itemsPerPage;
+ const endIndex = startIndex + this.itemsPerPage;
+
+ // Hide all items first
+ this.allProblemItems.forEach((item) => {
+ item.style.display = "none";
+ });
+
+ // Show current page items
+ this.filteredProblemItems.slice(startIndex, endIndex).forEach((item) => {
+ item.element.style.display = "";
+ });
+
+ // Update pagination controls
+ if (this.problemsPrevBtn) this.problemsPrevBtn.disabled = this.currentPage <= 1;
+ if (this.problemsNextBtn) this.problemsNextBtn.disabled = this.currentPage >= totalPages;
+
+ if (this.problemsPaginationInfo) {
+ this.problemsPaginationInfo.textContent =
+ totalPages > 0
+ ? `Page ${this.currentPage} of ${totalPages}`
+ : "No problems found";
+ }
+
+ this.setupPaginationLayout();
+ }
+
+ setupPaginationLayout() {
+ if (this.problemsPagination) {
+ Object.assign(this.problemsPagination.style, {
+ display: "flex",
+ justifyContent: "center",
+ position: "absolute",
+ left: "0",
+ right: "0",
+ bottom: "0",
+ margin: "0 auto",
+ width: "100%",
+ background: "inherit",
+ borderTop: "1px solid var(--border)",
+ padding: "12px 0"
+ });
+ // Style the pagination buttons and info text
+ const prevBtn = this.problemsPagination.querySelector('#problemsPrevBtn');
+ const nextBtn = this.problemsPagination.querySelector('#problemsNextBtn');
+ const infoText = this.problemsPagination.querySelector('#problemsPaginationInfo');
+ if (prevBtn) prevBtn.style.marginRight = '10px';
+ if (nextBtn) nextBtn.style.marginLeft = '10px';
+ if (infoText) infoText.style.marginTop = '2px';
+ this.problemsPagination.classList.remove("hidden");
+ }
+
+ if (this.problemsContainer?.parentElement) {
+ Object.assign(this.problemsContainer.parentElement.style, {
+ position: "relative",
+ paddingBottom: "56px"
+ });
+ }
+ }
+
+ showProblemDescription = async (item) => {
+ this.hideProblemDescription();
+
+ const folder = item.querySelector('a')?.getAttribute('href')?.split('/').pop();
+ if (!folder) return;
+
+ try {
+ let manifest = this.manifestCache.get(folder);
+
+ if (!manifest) {
+ // Try localStorage cache first
+ const cacheKey = `problem_manifest_${folder}`;
+ const cached = utils.safeLocalStorage.getItem(cacheKey);
+
+ if (cached) {
+ manifest = JSON.parse(cached);
+ this.manifestCache.set(folder, manifest);
+ } else {
+ // Fetch from API
+ const response = await fetch(`/api/problem_manifest/${encodeURIComponent(folder)}`);
+ manifest = response.ok ? await response.json() : { description: 'No description.' };
+
+ this.manifestCache.set(folder, manifest);
+ utils.safeLocalStorage.setItem(cacheKey, JSON.stringify(manifest));
+ }
+ }
+
+ this.createPopover(manifest.description || 'No description.', item);
+ } catch (error) {
+ console.warn("Failed to load problem description:", error);
+ this.createPopover('No description available.', item);
+ }
+ };
+
+ createPopover(description, item) {
+ this.problemDescriptionPopover = document.createElement("div");
+ this.problemDescriptionPopover.className = "problem-desc-popover";
+ this.problemDescriptionPopover.textContent = description;
+ document.body.appendChild(this.problemDescriptionPopover);
+
+ const rect = item.getBoundingClientRect();
+ Object.assign(this.problemDescriptionPopover.style, {
+ position: "fixed",
+ left: `${rect.left + window.scrollX}px`,
+ top: `${rect.bottom + window.scrollY + 6}px`,
+ zIndex: "1000",
+ minWidth: `${rect.width}px`
+ });
+ }
+
+ hideProblemDescription = () => {
+ if (this.problemDescriptionPopover) {
+ this.problemDescriptionPopover.remove();
+ this.problemDescriptionPopover = null;
+ }
+ };
+
+ attachProblemHoverEvents() {
+ this.allProblemItems.forEach((item) => {
+ item.addEventListener("mouseenter", () => this.showProblemDescription(item));
+ item.addEventListener("mouseleave", this.hideProblemDescription);
+ item.addEventListener("mousemove", this.handleMouseMove);
+ });
+ }
+
+ handleMouseMove = utils.throttle((e) => {
+ if (this.problemDescriptionPopover) {
+ this.problemDescriptionPopover.style.left = `${e.clientX + 10}px`;
+ }
+ }, 16); // ~60fps
+
+ sortProblemItems(column, direction) {
+ this.filteredProblemItems.sort((a, b) => {
+ let valueA, valueB;
+
+ switch (column) {
+ case "alpha":
+ valueA = a.name;
+ valueB = b.name;
+ break;
+ case "difficulty":
+ const difficultyOrder = { easy: 1, medium: 2, hard: 3 };
+ valueA = difficultyOrder[a.difficulty] || 0;
+ valueB = difficultyOrder[b.difficulty] || 0;
+ break;
+ default:
+ return 0;
+ }
+
+ let comparison = 0;
+ if (typeof valueA === "number" && typeof valueB === "number") {
+ comparison = valueA - valueB;
+ } else {
+ comparison = valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
+ }
+
+ return direction === "asc" ? comparison : -comparison;
+ });
+ }
+
+ attachEventListeners() {
+ this.problemsPrevBtn?.addEventListener("click", () => {
+ if (this.currentPage > 1) {
+ this.currentPage--;
+ this.updatePagination();
+ }
+ });
+
+ this.problemsNextBtn?.addEventListener("click", () => {
+ const totalPages = Math.ceil(this.filteredProblemItems.length / this.itemsPerPage);
+ if (this.currentPage < totalPages) {
+ this.currentPage++;
+ this.updatePagination();
+ }
+ });
+
+ this.problemSearch?.addEventListener("input", utils.debounce(() => {
+ this.filterProblems();
+ this.currentPage = 1;
+ this.updatePagination();
+ }, 300));
+
+ this.difficultyFilter?.addEventListener("change", () => {
+ this.filterProblems();
+ this.currentPage = 1;
+ this.updatePagination();
+ });
+
+ this.sortProblems?.addEventListener("change", () => {
+ const value = this.sortProblems.value;
+ if (value === "alpha" || value === "difficulty") {
+ if (this.problemSort.column === value) {
+ this.problemSort.direction = this.problemSort.direction === "asc" ? "desc" : "asc";
+ } else {
+ this.problemSort.column = value;
+ this.problemSort.direction = "asc";
+ }
+ this.sortProblemItems(this.problemSort.column, this.problemSort.direction);
+ this.currentPage = 1;
+ this.updatePagination();
+ }
+ });
+ }
+
+ filterProblems() {
+ const searchTerm = (this.problemSearch?.value || "").toLowerCase().trim();
+ const difficulty = this.difficultyFilter?.value || "all";
+
+ this.filteredProblemItems = this.allProblemItems
+ .map(this.getProblemData)
+ .filter(item => {
+ const matchesSearch = !searchTerm ||
+ item.name.includes(searchTerm) ||
+ item.desc.includes(searchTerm);
+
+ const matchesDifficulty = difficulty === "all" ||
+ item.difficulty === difficulty;
+
+ return matchesSearch && matchesDifficulty;
+ });
+ }
+
+ injectPopoverCSS() {
+ if (document.getElementById("problem-desc-popover-style")) return;
+
+ const style = document.createElement("style");
+ style.id = "problem-desc-popover-style";
+ style.textContent = `
+ .problem-desc-popover {
+ background: var(--card, #fff);
+ color: var(--text, #222);
+ border: 1px solid var(--border, #e5e7eb);
+ border-radius: 8px;
+ box-shadow: 0 4px 16px rgba(16,24,40,0.13);
+ padding: 12px 16px;
+ font-size: 0.98rem;
+ max-width: 350px;
+ min-width: 180px;
+ pointer-events: none;
+ opacity: 0.97;
+ transition: opacity 0.2s;
+ word-break: break-word;
+ }
+ html.dark .problem-desc-popover {
+ background: var(--card, #1e293b);
+ color: var(--text, #f1f5f9);
+ border: 1px solid var(--border, #334155);
+ }
+ `;
+ document.head.appendChild(style);
+ }
+
+ destroy() {
+ // Clean up event listeners and resources
+ this.hideProblemDescription();
+ this.manifestCache.clear();
+ }
+ }
+
+ // Leaderboard Manager
+ class LeaderboardManager {
+ constructor() {
+ this.problemFilter = document.getElementById("problemFilter");
+ this.runtimeFilter = document.getElementById("runtimeFilter");
+ this.leaderboardBody = document.getElementById("leaderboardBody");
+ this.sortableHeaders = document.querySelectorAll(".sortable");
+ this.rankInfoBtn = document.getElementById("rankInfoBtn");
+ this.rankingExplanation = document.getElementById("rankingExplanation");
+
+ this.currentSort = { column: "rank", direction: "asc" };
+ this.allRows = [];
+ this.filteredRows = [];
+ this.currentPage = 1;
+ this.itemsPerPage = 5;
+ this.leaderboardPagination = document.createElement("div");
+ this.leaderboardPagination.className = "pagination-controls";
+ this.leaderboardPagination.style.display = "flex";
+ this.leaderboardPagination.style.justifyContent = "center";
+ this.leaderboardPagination.style.position = "absolute";
+ this.leaderboardPagination.style.left = 0;
+ this.leaderboardPagination.style.right = 0;
+ this.leaderboardPagination.style.bottom = 0;
+ this.leaderboardPagination.style.margin = "0 auto 0 auto";
+ this.leaderboardPagination.style.width = "100%";
+ this.leaderboardPagination.style.background = "inherit";
+ this.leaderboardPagination.style.borderTop = "1px solid var(--border)";
+ this.leaderboardPagination.style.padding = "12px 0";
+ this.leaderboardPagination.innerHTML = `
+
+
+
+ `;
+ this.leaderboardPrevBtn = this.leaderboardPagination.querySelector("#leaderboardPrevBtn");
+ this.leaderboardNextBtn = this.leaderboardPagination.querySelector("#leaderboardNextBtn");
+ this.leaderboardPaginationInfo = this.leaderboardPagination.querySelector("#leaderboardPaginationInfo");
+ this.init();
+ }
+
+ init() {
+ if (!this.leaderboardBody || this.leaderboardBody.children.length === 0) return;
+ this.initializeRows();
+ this.attachEventListeners();
+ this.filterLeaderboard();
+ this.setInitialSortIndicator();
+ // Insert pagination controls after leaderboard table
+ const leaderboardContainer = document.getElementById("leaderboardContainer");
+ if (leaderboardContainer && !leaderboardContainer.contains(this.leaderboardPagination)) {
+ leaderboardContainer.appendChild(this.leaderboardPagination);
+ // Ensure parent card is relatively positioned and has enough bottom padding
+ const leaderboardCard = leaderboardContainer.closest('.card');
+ if (leaderboardCard) {
+ leaderboardCard.style.position = "relative";
+ leaderboardCard.style.paddingBottom = "56px";
+ }
+ }
+ // Also ensure the parent card (section.card) contains the controls for correct layout
+ const leaderboardCard = leaderboardContainer?.closest('.card');
+ if (leaderboardCard && !leaderboardCard.contains(this.leaderboardPagination)) {
+ leaderboardCard.appendChild(this.leaderboardPagination);
+ }
+ }
+
+ initializeRows() {
+ this.allRows = Array.from(this.leaderboardBody.querySelectorAll("tr")).map((row, index) => ({
+ element: row,
+ user: row.dataset.user || "",
+ problem: row.dataset.problem || "",
+ runtime: parseFloat(row.dataset.runtime) || 0,
+ memory: parseFloat(row.dataset.memory) || 0,
+ timestamp: new Date(row.dataset.timestamp || Date.now()).getTime(),
+ language: row.dataset.language || "",
+ originalIndex: index,
+ }));
+ }
+
+ filterLeaderboard() {
+ const problemTerm = (this.problemFilter?.value || "").toLowerCase().trim();
+ const runtimeType = this.runtimeFilter?.value || "all";
+ // Filter rows
+ this.filteredRows = this.allRows.filter((rowData) => {
+ let visible = true;
+ if (problemTerm) {
+ visible = rowData.problem.toLowerCase().includes(problemTerm);
+ }
+ return visible;
+ });
+ // Apply runtime filter (best/worst per user per problem)
+ if (runtimeType === "best" || runtimeType === "worst") {
+ const userProblemGroups = {};
+ this.filteredRows.forEach((rowData) => {
+ const key = `${rowData.user}::${rowData.problem}`;
+ if (!userProblemGroups[key]) userProblemGroups[key] = [];
+ userProblemGroups[key].push(rowData);
+ });
+ this.filteredRows = Object.values(userProblemGroups).flatMap((group) => {
+ if (group.length <= 1) return group;
+ group.sort((a, b) => a.runtime - b.runtime);
+ const keepIndex = runtimeType === "best" ? 0 : group.length - 1;
+ return [group[keepIndex]];
+ });
+ }
+ this.currentPage = 1;
+ this.updateLeaderboardPagination();
+ }
+
+ updateLeaderboardPagination() {
+ const totalPages = Math.ceil(this.filteredRows.length / this.itemsPerPage) || 1;
+ if (this.currentPage > totalPages) this.currentPage = totalPages;
+ const startIndex = (this.currentPage - 1) * this.itemsPerPage;
+ const endIndex = startIndex + this.itemsPerPage;
+ // Hide all rows first
+ this.allRows.forEach((rowData) => {
+ rowData.element.style.display = "none";
+ });
+ // Show only current page rows
+ this.filteredRows.slice(startIndex, endIndex).forEach((rowData) => {
+ rowData.element.style.display = "";
+ });
+ // Update pagination controls
+ if (this.leaderboardPrevBtn) this.leaderboardPrevBtn.disabled = this.currentPage <= 1;
+ if (this.leaderboardNextBtn) this.leaderboardNextBtn.disabled = this.currentPage >= totalPages;
+ if (this.leaderboardPaginationInfo) {
+ this.leaderboardPaginationInfo.textContent =
+ totalPages > 0 ? `Page ${this.currentPage} of ${totalPages}` : "No entries found";
+ }
+ // Always show and center pagination at the bottom of the leaderboard card
+ if (this.leaderboardPagination) {
+ this.leaderboardPagination.classList.remove("hidden");
+ this.leaderboardPagination.style.display = "flex";
+ this.leaderboardPagination.style.justifyContent = "center";
+ this.leaderboardPagination.style.position = "absolute";
+ this.leaderboardPagination.style.left = 0;
+ this.leaderboardPagination.style.right = 0;
+ this.leaderboardPagination.style.bottom = 0;
+ this.leaderboardPagination.style.margin = "0 auto 0 auto";
+ this.leaderboardPagination.style.width = "100%";
+ this.leaderboardPagination.style.background = "inherit";
+ this.leaderboardPagination.style.borderTop = "1px solid var(--border)";
+ this.leaderboardPagination.style.padding = "12px 0";
+ }
+ // Make sure the parent leaderboard card is relatively positioned
+ const leaderboardContainer = document.getElementById("leaderboardContainer");
+ if (leaderboardContainer && leaderboardContainer.parentElement) {
+ leaderboardContainer.parentElement.style.position = "relative";
+ leaderboardContainer.parentElement.style.paddingBottom = "56px";
+ }
+ // Recalculate ranks for visible rows
+ this.calculateOverallRanking();
+ }
+
+ calculateOverallRanking() {
+ // Only consider visible rows (current page)
+ const visibleRows = this.filteredRows.slice(
+ (this.currentPage - 1) * this.itemsPerPage,
+ (this.currentPage - 1) * this.itemsPerPage + this.itemsPerPage
+ );
+ if (visibleRows.length === 0) return;
+ // Group submissions by problem to find the best performance for each
+ const problemBests = {};
+ visibleRows.forEach((rowData) => {
+ const problem = rowData.problem;
+ if (!problemBests[problem]) {
+ problemBests[problem] = {
+ bestRuntime: Infinity,
+ bestMemory: Infinity,
+ };
+ }
+ problemBests[problem].bestRuntime = Math.min(
+ problemBests[problem].bestRuntime,
+ rowData.runtime
+ );
+ problemBests[problem].bestMemory = Math.min(
+ problemBests[problem].bestMemory,
+ rowData.memory
+ );
+ });
+ // Calculate normalized scores for each submission
+ visibleRows.forEach((rowData) => {
+ const problemBest = problemBests[rowData.problem];
+ const runtimeScore =
+ problemBest.bestRuntime > 0
+ ? rowData.runtime / problemBest.bestRuntime
+ : 1;
+ const memoryScore =
+ problemBest.bestMemory > 0
+ ? rowData.memory / problemBest.bestMemory
+ : 1;
+ rowData.overallScore = runtimeScore * 0.7 + memoryScore * 0.3;
+ });
+ // Sort by overall score (lower is better), then by timestamp (earlier is better for ties)
+ visibleRows.sort((a, b) => {
+ const scoreDiff = a.overallScore - b.overallScore;
+ if (Math.abs(scoreDiff) > 0.000001) return scoreDiff;
+ return a.timestamp - b.timestamp;
+ });
+ // Reorder DOM elements and update ranks
+ const fragment = document.createDocumentFragment();
+ visibleRows.forEach((rowData, index) => {
+ fragment.appendChild(rowData.element);
+ // Update rank cell
+ const rankCell = rowData.element.cells[0];
+ if (rankCell) rankCell.textContent = index + 1 + (this.currentPage - 1) * this.itemsPerPage;
+ // Update rank classes
+ rowData.element.className = rowData.element.className.replace(/\brank-\d+\b/g, "");
+ if (index === 0) rowData.element.classList.add("rank-1");
+ else if (index < 3) rowData.element.classList.add("rank-top3");
+ });
+ this.leaderboardBody.appendChild(fragment);
+ // this.updateRankClasses(); // Function does not exist, so remove this call
+ }
+
+ attachEventListeners() {
+ // Sorting event listeners
+ this.sortableHeaders.forEach((header) => {
+ header.addEventListener("click", () => {
+ const column = header.dataset.sort;
+ if (!column) return;
+ // Remove sorting classes from all headers
+ this.sortableHeaders.forEach((h) => h.classList.remove("sort-asc", "sort-desc"));
+ // Toggle sort direction
+ if (this.currentSort.column === column) {
+ this.currentSort.direction = this.currentSort.direction === "asc" ? "desc" : "asc";
+ } else {
+ this.currentSort.column = column;
+ this.currentSort.direction = "asc";
+ }
+ // Add sorting class to current header
+ header.classList.add(`sort-${this.currentSort.direction}`);
+ // Sort filteredRows
+ this.filteredRows.sort((a, b) => {
+ let valueA = a[column];
+ let valueB = b[column];
+ if (typeof valueA === "string") valueA = valueA.toLowerCase();
+ if (typeof valueB === "string") valueB = valueB.toLowerCase();
+ let comparison = 0;
+ if (typeof valueA === "number" && typeof valueB === "number") {
+ comparison = valueA - valueB;
+ } else {
+ comparison = valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
+ }
+ return this.currentSort.direction === "asc" ? comparison : -comparison;
+ });
+ this.currentPage = 1;
+ this.updateLeaderboardPagination();
+ });
+ });
+
+ // Filter event listeners with debouncing
+ this.problemFilter?.addEventListener("input", utils.debounce(() => {
+ this.filterLeaderboard();
+ }, 300));
+ this.runtimeFilter?.addEventListener("change", () => this.filterLeaderboard());
+
+ // Pagination event listeners
+ this.leaderboardPrevBtn?.addEventListener("click", () => {
+ if (this.currentPage > 1) {
+ this.currentPage--;
+ this.updateLeaderboardPagination();
+ }
+ });
+ this.leaderboardNextBtn?.addEventListener("click", () => {
+ const totalPages = Math.ceil(this.filteredRows.length / this.itemsPerPage) || 1;
+ if (this.currentPage < totalPages) {
+ this.currentPage++;
+ this.updateLeaderboardPagination();
+ }
+ });
+
+ // Rank info popout
+ this.rankInfoBtn?.addEventListener("click", (e) => {
+ e.preventDefault();
+ this.rankingExplanation?.classList.toggle("active");
+ this.rankInfoBtn?.classList.toggle("active");
+ });
+
+ // Close ranking explanation when clicking outside
+ document.addEventListener("click", (e) => {
+ if (
+ this.rankingExplanation?.classList.contains("active") &&
+ !this.rankingExplanation.contains(e.target) &&
+ !this.rankInfoBtn?.contains(e.target)
+ ) {
+ this.rankingExplanation.classList.remove("active");
+ this.rankInfoBtn?.classList.remove("active");
+ }
+ });
+ }
+
+ setInitialSortIndicator() {
+ const defaultHeader = document.querySelector('[data-sort="rank"]');
+ if (defaultHeader) {
+ defaultHeader.classList.add("sort-asc");
+ }
+ }
+ }
+
+ // Initialize all managers
+ const darkModeManager = new DarkModeManager();
+ const problemManager = new ProblemManager();
+ const leaderboardManager = new LeaderboardManager();
+
+ // Apply dark mode to dynamically created elements
+ const applyDarkModeToElements = () => {
+ // Any additional dark mode styling for dynamically created elements can go here
+ };
+
+ // Watch for dark mode changes
+ const darkModeObserver = new MutationObserver(applyDarkModeToElements);
+ darkModeObserver.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ["class"],
+ });
+
+ // Cleanup on page unload
+ window.addEventListener("beforeunload", () => {
+ problemManager.destroy();
+ darkModeObserver.disconnect();
+ });
});
\ No newline at end of file
diff --git a/src/app.py b/src/app.py
index 2979d2c..2c9a593 100644
--- a/src/app.py
+++ b/src/app.py
@@ -45,9 +45,14 @@ def api_problem_manifest(folder):
except Exception as e:
return jsonify({'error': str(e)}), 500
+# I introduce you to the fucking JavaScript shit routes, fuck javascripts
+@app.route('/JavaScript/')
+def serve_js(filename):
+ return send_from_directory('JavaScript', filename)
+
@app.route("/script.js")
def script():
- return send_from_directory("templates", "script.js")
+ return send_from_directory("JavaScript", "script.js")
@app.route('/favicon.ico')
def favicon():
diff --git a/src/templates/index.html b/src/templates/index.html
index 744137b..c71cdf6 100644
--- a/src/templates/index.html
+++ b/src/templates/index.html
@@ -69,7 +69,7 @@
Leaderboard
- ℹ️
+
@@ -141,6 +141,6 @@
-
+