diff --git a/run.bash b/run.bash index 06346d0..80f5336 100644 --- a/run.bash +++ b/run.bash @@ -1,17 +1,17 @@ -#!/bin/bash - -set -e # exit if any command fails - -# Ensure QPP/database directory exists -mkdir -p src/database - -python -m venv venv -source venv/bin/activate - -pip install --upgrade pip -pip install -r requirements.txt - -export FLASK_APP=src.app -export FLASK_ENV=production - -flask run --host=0.0.0.0 --port=5000 +u!/bin/bash + +set -e # exit if any command fails + +# Ensure QPP/database directory exists +mkdir -p src/database + +python -m venv venv +source venv/bin/activate + +pip install --upgrade pip +pip install -r requirements.txt + +export FLASK_APP=src.app +export FLASK_ENV=production + +flask run --host=0.0.0.0 --port=5000 diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..ba180fe --- /dev/null +++ b/run.bat @@ -0,0 +1 @@ +python -m flask --app .\src\app.py run --host=0.0.0.0 --port=5000 \ No newline at end of file diff --git a/src/static/index.css b/src/static/index.css index fad9bcb..0ebf5fa 100644 --- a/src/static/index.css +++ b/src/static/index.css @@ -1,207 +1,326 @@ -:root { - --bg: #f6f8fb; - --card: #fff; - --muted: #6b7280; - --accent: #2563eb; - --shadow: 0 4px 12px rgba(16, 24, 40, 0.06); - --radius: 8px; - --mono: 'JetBrains Mono', monospace; -} -* { box-sizing: border-box; margin: 0; padding: 0; } -html, body { - height: 100%; -} -body { - font-family: Inter, sans-serif; - background: var(--bg); - color: #0f172a; - padding: 16px; - display: flex; - justify-content: center; - align-items: center; -} -.wrap { - width: 100%; - max-width: 1100px; -} -header { - margin-bottom: 14px; -} -header h1 { - text-align: center; - font-size: 1.6rem; - color: #111827; -} -header p { - color: var(--muted); - font-size: 0.9rem; -} -.content { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 12px; -} -.content.single-column { - grid-template-columns: 1fr; -} -.card { - background: var(--card); - border-radius: var(--radius); - box-shadow: var(--shadow); - padding: 12px; -} -/* Search/filter controls */ -.search-controls { - margin-bottom: 12px; - display: flex; - gap: 8px; -} -.search-input { - flex: 1; - padding: 6px 10px; - border: 1px solid #e5e7eb; - border-radius: 4px; - font-size: 0.9rem; -} -.filter-select { - padding: 6px 8px; - border: 1px solid #e5e7eb; - border-radius: 4px; - font-size: 0.9rem; - background: white; -} -/* Problems list */ -.problems-list .problem-item { - padding: 8px; - border-bottom: 1px solid #e5e7eb; - display: flex; - justify-content: space-between; - align-items: center; -} -.problem-item:last-child { - border-bottom: none; -} -.problem-item a { - text-decoration: none; - color: #0077ff; - font-weight: 600; -} -/* Difficulty badge */ -.difficulty { - display: inline-flex; - align-items: center; - padding: 0.25em 0.6em; - border-radius: 10px; - font-size: 0.85em; - font-weight: bold; - text-transform: uppercase; - color: white; - white-space: nowrap; -} -.difficulty[data-difficulty="easy"] { - background-color: #4CAF50; /* Green */ -} -.difficulty[data-difficulty="medium"] { - background-color: #FFC107; /* Amber */ - color: #333; -} -.difficulty[data-difficulty="hard"] { - background-color: #F44336; /* Red */ -} -/* Leaderboard */ -.leaderboard-head { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 6px; -} -.leaderboard-controls { - display: flex; - gap: 8px; - margin-bottom: 12px; -} -.leaderboard-table { - width: 100%; - border-collapse: collapse; - font-size: 0.9rem; -} -.leaderboard-table th, -.leaderboard-table td { - padding: 6px 8px; - border-bottom: 1px solid #e5e7eb; - text-align: left; -} -.leaderboard-table th { - background: #f9fafb; - font-weight: 600; - color: var(--muted); -} -.leaderboard-table tr:hover { - background: #f3f4f6; -} -/* Sort indicators */ -.sortable { - cursor: pointer; - position: relative; - padding-right: 16px; -} -.sortable::after { - content: "↕"; - position: absolute; - right: 4px; - top: 50%; - transform: translateY(-50%); - font-size: 0.8em; - opacity: 0.5; -} -.sort-asc::after { - content: "↑"; - opacity: 1; -} -.sort-desc::after { - content: "↓"; - opacity: 1; -} -/* Toggle button */ -.btn { - border: none; - background: transparent; - cursor: pointer; - color: var(--accent); - font-size: 0.85rem; - padding: 4px 6px; - border-radius: 4px; -} -.btn:hover { - background: rgba(37, 99, 235, 0.08); -} -.btn.active { - background: rgba(37, 99, 235, 0.15); -} -@media (max-width: 800px) { - .content { grid-template-columns: 1fr; } - .leaderboard-controls { - flex-direction: column; - } -} - -/* Leaderboard horizontal collapse */ -#leaderboardSection { - transition: max-width 0.35s ease, opacity 0.25s ease; - overflow: hidden; - max-width: 100%; -} - -#leaderboardSection.hidden { - max-width: 0; - opacity: 0; - pointer-events: none; -} - -#leaderboardSection.visible { - max-width: 100%; /* take full available space in grid column */ - opacity: 1; -} -#rankingExplanation { - transition: all 0.35s ease; -} +:root { + --bg: #f6f8fb; + --card: #fff; + --text: #0f172a; + --muted: #6b7280; + --accent: #2563eb; + --border: #e5e7eb; + --hover: #f3f4f6; + --shadow: 0 4px 12px rgba(16, 24, 40, 0.06); + --radius: 8px; + --mono: "JetBrains Mono", monospace; +} + +/* Dark mode variables */ +html.dark { + --bg: #0f172a; + --card: #1e293b; + --text: #f1f5f9; + --muted: #94a3b8; + --accent: #3b82f6; + --border: #334155; + --hover: #334155; + --shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} +html, +body { + height: 100%; +} +body { + font-family: Inter, sans-serif; + background: var(--bg); + color: var(--text); + padding: 16px; + display: flex; + justify-content: center; + align-items: center; + transition: + background-color 0.3s ease, + color 0.3s ease; +} +.wrap { + width: 100%; + max-width: 1100px; +} +header { + margin-bottom: 14px; +} +.header-content { + display: flex; + justify-content: center; + align-items: center; + position: relative; +} +header h1 { + text-align: center; + font-size: 1.6rem; + color: var(--text); +} +header p { + color: var(--muted); + font-size: 0.9rem; +} +.dark-mode-toggle { + position: absolute; + right: 0; + background: none; + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 6px 10px; + cursor: pointer; + color: var(--text); + font-size: 1.2rem; + transition: all 0.3s ease; +} +.dark-mode-toggle:hover { + background: var(--hover); + transform: scale(1.05); +} +html.dark .dark-mode-icon::before { + content: "☀︎️"; +} +html:not(.dark) .dark-mode-icon::before { + content: "⏾"; +} +.dark-mode-icon { + display: inline-block; +} +.dark-mode-icon::before { + font-size: 1em; +} +.content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; +} +.content.single-column { + grid-template-columns: 1fr; +} +.card { + background: var(--card); + border-radius: var(--radius); + box-shadow: var(--shadow); + padding: 12px; +} +/* Search/filter controls */ +.search-controls { + margin-bottom: 12px; + display: flex; + gap: 8px; +} +.search-input { + flex: 1; + padding: 6px 10px; + border: 1px solid var(--border); + border-radius: 4px; + font-size: 0.9rem; + background: var(--card); + color: var(--text); + transition: border-color 0.3s ease; +} +.search-input:focus { + outline: none; + border-color: var(--accent); +} +.filter-select { + padding: 6px 8px; + border: 1px solid var(--border); + border-radius: 4px; + font-size: 0.9rem; + background: var(--card); + color: var(--text); + transition: border-color 0.3s ease; +} +.filter-select:focus { + outline: none; + border-color: var(--accent); +} +/* Problems list */ +.problems-list .problem-item { + padding: 8px; + border-bottom: 1px solid var(--border); + display: flex; + justify-content: space-between; + align-items: center; + transition: background-color 0.3s ease; +} +.problem-item:hover { + background: var(--hover); +} +.problem-item:last-child { + border-bottom: none; +} +.problem-item a { + text-decoration: none; + color: var(--accent); + font-weight: 600; + transition: color 0.3s ease; +} +.problem-item a:hover { + text-decoration: underline; +} +/* Difficulty badge */ +.difficulty { + display: inline-flex; + align-items: center; + padding: 0.25em 0.6em; + border-radius: 10px; + font-size: 0.85em; + font-weight: bold; + text-transform: uppercase; + color: white; + white-space: nowrap; +} +.difficulty[data-difficulty="easy"] { + background-color: #4caf50; /* Green */ +} +.difficulty[data-difficulty="medium"] { + background-color: #ffc107; /* Amber */ + color: #333; +} +.difficulty[data-difficulty="hard"] { + background-color: #f44336; /* Red */ +} +/* Leaderboard */ +.leaderboard-head { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; +} +.leaderboard-controls { + display: flex; + gap: 8px; + margin-bottom: 12px; +} +.leaderboard-table { + width: 100%; + border-collapse: collapse; + font-size: 0.9rem; +} +.leaderboard-table th, +.leaderboard-table td { + padding: 6px 8px; + border-bottom: 1px solid var(--border); + text-align: left; +} +.leaderboard-table th { + background: var(--hover); + font-weight: 600; + color: var(--muted); +} +.leaderboard-table tr:hover { + background: var(--hover); +} +/* Sort indicators */ +.sortable { + cursor: pointer; + position: relative; + padding-right: 16px; +} +.sortable::after { + content: "↕"; + position: absolute; + right: 4px; + top: 50%; + transform: translateY(-50%); + font-size: 0.8em; + opacity: 0.5; +} +.sort-asc::after { + content: "↑"; + opacity: 1; +} +.sort-desc::after { + content: "↓"; + opacity: 1; +} +/* Toggle button */ +.btn { + border: none; + background: transparent; + cursor: pointer; + color: var(--accent); + font-size: 0.85rem; + padding: 4px 6px; + border-radius: 4px; +} +.btn:hover { + background: rgba(37, 99, 235, 0.08); +} +.btn.active { + background: rgba(37, 99, 235, 0.15); +} +@media (max-width: 800px) { + .content { + grid-template-columns: 1fr; + } + .leaderboard-controls { + flex-direction: column; + } +} + +/* Leaderboard horizontal collapse */ +#leaderboardSection { + transition: + max-width 0.35s ease, + opacity 0.25s ease; + overflow: hidden; + max-width: 100%; +} + +#leaderboardSection.hidden { + max-width: 0; + opacity: 0; + pointer-events: none; +} + +#leaderboardSection.visible { + max-width: 100%; /* take full available space in grid column */ + opacity: 1; +} +#rankingExplanation { + transition: all 0.35s ease; +} + +/* Pagination Controls */ +.pagination-controls { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid var(--border); +} +.pagination-btn { + background: var(--card); + border: 1px solid var(--border); + border-radius: 4px; + padding: 6px 12px; + cursor: pointer; + color: var(--text); + font-size: 0.9rem; + transition: all 0.3s ease; +} +.pagination-btn:hover:not(:disabled) { + background: var(--hover); + border-color: var(--accent); +} +.pagination-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} +.pagination-info { + color: var(--muted); + font-size: 0.9rem; +} + +/* Hide pagination when not needed */ +.pagination-controls.hidden { + display: none; +} diff --git a/src/static/problem.css b/src/static/problem.css index 73572da..83037b4 100644 --- a/src/static/problem.css +++ b/src/static/problem.css @@ -1,212 +1,296 @@ -body { - font-family: 'Inter', sans-serif; - margin: 0; - padding: 0; - background-color: #f9f9f9; - color: #333; - min-height: 100vh; /* allow content to grow */ - overflow-y: auto; /* allow vertical scroll */ - box-sizing: border-box; -} - -*, *::before, *::after { - box-sizing: inherit; -} - -.main-container { - display: flex; - flex-wrap: wrap; /* wrap on small screens */ - min-height: 100vh; - width: 100vw; -} - -.problem-panel { - flex: 1 1 400px; /* grow/shrink with base 400px */ - min-width: 300px; - background: white; - overflow-y: auto; - padding: 20px; - border-right: 1px solid #eaeaea; - max-height: 100vh; -} - -.editor-container { - flex: 1 1 400px; - min-width: 300px; - display: flex; - flex-direction: column; - background: white; - max-height: 100vh; - overflow: hidden; /* internal scroll handling */ -} - -.editor-header { - padding: 15px 20px; - border-bottom: 1px solid #eaeaea; - flex-shrink: 0; -} - -.editor-wrapper { - flex: 1 1 auto; - display: flex; - flex-direction: column; - min-height: 0; - padding: 0 20px; - overflow-y: auto; -} - -.problem-header { - display: flex; - align-items: center; - margin-bottom: 20px; -} - -.back-btn { - background: none; - border: none; - cursor: pointer; - font-size: 16px; - color: #666; - margin-right: 15px; - padding: 5px; -} - -.back-btn:hover { - color: #000; -} - -h1 { - font-size: 22px; - font-weight: 600; - margin: 0; - color: #1a1a1a; -} - -.problem-desc { - line-height: 1.6; - font-size: 15px; - overflow-wrap: break-word; -} - -.problem-desc pre { - background: #f6f8fa; - padding: 12px; - border-radius: 4px; - overflow-x: auto; - font-family: 'JetBrains Mono', monospace; - font-size: 14px; -} - -.problem-desc code { - background: #f6f8fa; - padding: 2px 4px; - border-radius: 3px; - font-family: 'JetBrains Mono', monospace; - font-size: 14px; -} - -.editor-actions { - padding: 15px 0; - display: flex; - justify-content: flex-end; - flex-shrink: 0; -} - -.editor-actions button { - background-color: #007bff; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - cursor: pointer; - font-weight: 500; - font-size: 14px; -} - -.editor-actions button:hover { - background-color: #0069d9; -} - -#editor { - flex: 1 1 auto; - min-height: 300px; - border: 1px solid #ddd; - border-radius: 4px; - overflow: auto; - max-height: 60vh; -} - -.result-panel { - margin-top: 20px; - padding: 15px; - background: #f8f9fa; - border-radius: 4px; - margin-bottom: 20px; - min-height: 120px; - overflow-y: auto; - max-height: 30vh; -} - -.result-panel h3 { - margin-top: 0; - font-size: 16px; - margin-bottom: 10px; -} - -.result-panel pre { - background: #f6f8fa; - padding: 12px; - border-radius: 4px; - overflow-x: auto; - white-space: pre-wrap; - font-family: 'JetBrains Mono', monospace; - font-size: 14px; - margin: 5px 0; -} - -.placeholder { - color: #999; - font-style: italic; - text-align: center; - padding: 20px; -} - -label { - display: block; - margin-bottom: 5px; - font-size: 14px; - color: #666; -} - -input[type="text"] { - width: 100%; - padding: 8px; - border: 1px solid #ddd; - border-radius: 4px; - margin-bottom: 15px; - font-family: 'Inter', sans-serif; -} - -/* Responsive adjustments */ -@media (max-width: 768px) { - .main-container { - flex-direction: column; - height: auto; - overflow-y: visible; - } - .problem-panel, .editor-container { - flex: none; - width: 100%; - min-width: auto; - max-height: none; - border-right: none; - border-bottom: 1px solid #eaeaea; - } - #editor { - min-height: 400px; - max-height: none; - } - .result-panel { - max-height: none; - } -} \ No newline at end of file +:root { + --bg: #f9f9f9; + --card: #fff; + --text: #333; + --muted: #666; + --accent: #007bff; + --accent-hover: #0069d9; + --border: #eaeaea; + --hover: #f8f9fa; + --code-bg: #f6f8fa; + --editor-border: #ddd; +} + +html.dark { + --bg: #0f172a; + --card: #1e293b; + --text: #f1f5f9; + --muted: #94a3b8; + --accent: #3b82f6; + --accent-hover: #2563eb; + --border: #334155; + --hover: #334155; + --code-bg: #1e293b; + --editor-border: #475569; +} + +body { + font-family: "Inter", sans-serif; + margin: 0; + padding: 0; + background-color: var(--bg); + color: var(--text); + min-height: 100vh; /* allow content to grow */ + overflow-y: auto; /* allow vertical scroll */ + box-sizing: border-box; + transition: + background-color 0.3s ease, + color 0.3s ease; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + +.main-container { + display: flex; + flex-wrap: wrap; /* wrap on small screens */ + min-height: 100vh; + width: 100vw; +} + +.problem-panel { + flex: 1 1 400px; /* grow/shrink with base 400px */ + min-width: 300px; + background: var(--card); + overflow-y: auto; + padding: 20px; + border-right: 1px solid var(--border); + max-height: 100vh; + transition: background-color 0.3s ease; +} + +.editor-container { + flex: 1 1 400px; + min-width: 300px; + display: flex; + flex-direction: column; + background: var(--card); + max-height: 100vh; + overflow: hidden; /* internal scroll handling */ + transition: background-color 0.3s ease; +} + +.editor-header { + padding: 15px 20px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} + +.editor-wrapper { + flex: 1 1 auto; + display: flex; + flex-direction: column; + min-height: 0; + padding: 0 20px; + overflow-y: auto; +} + +.problem-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20px; +} + +.back-btn { + background: none; + border: 1px solid var(--border); + border-radius: 4px; + cursor: pointer; + font-size: 16px; + color: var(--muted); + margin-right: 15px; + padding: 6px 10px; + transition: all 0.3s ease; +} + +.back-btn:hover { + color: var(--text); + background: var(--hover); +} + +.dark-mode-toggle { + background: none; + border: 1px solid var(--border); + border-radius: 4px; + padding: 6px 10px; + cursor: pointer; + color: var(--text); + font-size: 1.2rem; + transition: all 0.3s ease; +} + +.dark-mode-toggle:hover { + background: var(--hover); + transform: scale(1.05); +} + +html.dark .dark-mode-icon::before { + content: "☀"; +} + +html:not(.dark) .dark-mode-icon::before { + content: "⏾"; +} + +.dark-mode-icon { + display: inline-block; +} + +.dark-mode-icon::before { + font-size: 1em; +} + +h1 { + font-size: 22px; + font-weight: 600; + margin: 0; + color: var(--text); + flex: 1; +} + +.problem-desc { + line-height: 1.6; + font-size: 15px; + overflow-wrap: break-word; +} + +.problem-desc pre { + background: var(--code-bg); + padding: 12px; + border-radius: 4px; + overflow-x: auto; + font-family: "JetBrains Mono", monospace; + font-size: 14px; + border: 1px solid var(--border); +} + +.problem-desc code { + background: var(--code-bg); + padding: 2px 4px; + border-radius: 3px; + font-family: "JetBrains Mono", monospace; + font-size: 14px; +} + +.editor-actions { + padding: 15px 0; + display: flex; + justify-content: flex-end; + flex-shrink: 0; +} + +.editor-actions button { + background-color: var(--accent); + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-weight: 500; + font-size: 14px; + transition: background-color 0.3s ease; +} + +.editor-actions button:hover { + background-color: var(--accent-hover); +} + +#editor { + flex: 1 1 auto; + min-height: 300px; + border: 1px solid var(--editor-border); + border-radius: 4px; + overflow: auto; + max-height: 60vh; +} + +.result-panel { + margin-top: 20px; + padding: 15px; + background: var(--hover); + border-radius: 4px; + margin-bottom: 20px; + min-height: 120px; + overflow-y: auto; + max-height: 30vh; + border: 1px solid var(--border); + transition: background-color 0.3s ease; +} + +.result-panel h3 { + margin-top: 0; + font-size: 16px; + margin-bottom: 10px; +} + +.result-panel pre { + background: var(--code-bg); + padding: 12px; + border-radius: 4px; + overflow-x: auto; + white-space: pre-wrap; + font-family: "JetBrains Mono", monospace; + font-size: 14px; + margin: 5px 0; + border: 1px solid var(--border); +} + +.placeholder { + color: var(--muted); + font-style: italic; + text-align: center; + padding: 20px; +} + +label { + display: block; + margin-bottom: 5px; + font-size: 14px; + color: var(--muted); +} + +input[type="text"] { + width: 100%; + padding: 8px; + border: 1px solid var(--border); + border-radius: 4px; + margin-bottom: 15px; + font-family: "Inter", sans-serif; + background: var(--card); + color: var(--text); + transition: border-color 0.3s ease; +} + +input[type="text"]:focus { + outline: none; + border-color: var(--accent); +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .main-container { + flex-direction: column; + height: auto; + overflow-y: visible; + } + .problem-panel, + .editor-container { + flex: none; + width: 100%; + min-width: auto; + max-height: none; + border-right: none; + border-bottom: 1px solid var(--border); + } + #editor { + min-height: 400px; + max-height: none; + } + .result-panel { + max-height: none; + } +} diff --git a/src/static/script.js b/src/static/script.js new file mode 100644 index 0000000..38d271f --- /dev/null +++ b/src/static/script.js @@ -0,0 +1,403 @@ +document.addEventListener("DOMContentLoaded", () => { + // Dark mode functionality + const darkModeToggle = document.getElementById("darkModeToggle"); + const html = document.documentElement; + + // Load saved dark mode preference + const savedDarkMode = localStorage.getItem("darkMode"); + if ( + savedDarkMode === "true" || + (savedDarkMode === null && + // detect if the user already has a dark mode enabled in the system settings ( works for all systems ) + window.matchMedia("(prefers-color-scheme: dark)").matches) + ) { + html.classList.add("dark"); + } + + darkModeToggle?.addEventListener("click", () => { + html.classList.toggle("dark"); + localStorage.setItem("darkMode", html.classList.contains("dark")); + }); + + // Problem search and pagination + const problemSearch = document.getElementById("problemSearch"); + const problemsContainer = document.getElementById("problemsContainer"); + const problemsPagination = document.getElementById("problemsPagination"); + const problemsPrevBtn = document.getElementById("problemsPrevBtn"); + const problemsNextBtn = document.getElementById("problemsNextBtn"); + const problemsPaginationInfo = document.getElementById( + "problemsPaginationInfo", + ); + + let allProblemItems = []; + let filteredProblemItems = []; + let currentPage = 1; + const itemsPerPage = 5; + + // Initialize problem items + function initializeProblemItems() { + allProblemItems = Array.from( + problemsContainer?.querySelectorAll(".problem-item") || [], + ); + filteredProblemItems = [...allProblemItems]; + updatePagination(); + } + + function updatePagination() { + const totalPages = Math.ceil(filteredProblemItems.length / itemsPerPage); + const startIndex = (currentPage - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + + // Hide all items first + allProblemItems.forEach((item) => { + item.style.display = "none"; + }); + + // Show current page items + filteredProblemItems.slice(startIndex, endIndex).forEach((item) => { + item.style.display = ""; + }); + + // Update pagination controls + if (problemsPrevBtn) problemsPrevBtn.disabled = currentPage <= 1; + if (problemsNextBtn) problemsNextBtn.disabled = currentPage >= totalPages; + if (problemsPaginationInfo) { + problemsPaginationInfo.textContent = + totalPages > 0 + ? `Page ${currentPage} of ${totalPages}` + : "No problems found"; + } + + // Hide pagination if not needed + if (problemsPagination) { + problemsPagination.classList.toggle("hidden", totalPages <= 1); + } + } + + function filterProblems() { + const term = problemSearch?.value.toLowerCase().trim() || ""; + filteredProblemItems = allProblemItems.filter((item) => { + const name = item.dataset.name?.toLowerCase() || ""; + const desc = item.dataset.desc?.toLowerCase() || ""; + return !term || name.includes(term) || desc.includes(term); + }); + currentPage = 1; + updatePagination(); + } + + // Event listeners for pagination + problemsPrevBtn?.addEventListener("click", () => { + if (currentPage > 1) { + currentPage--; + updatePagination(); + } + }); + + problemsNextBtn?.addEventListener("click", () => { + const totalPages = Math.ceil(filteredProblemItems.length / itemsPerPage); + if (currentPage < totalPages) { + currentPage++; + updatePagination(); + } + }); + + problemSearch?.addEventListener("input", filterProblems); + + // Initialize problems pagination + if (problemsContainer) { + initializeProblemItems(); + } + + // Leaderboard functionality + const problemFilter = document.getElementById("problemFilter"); + const runtimeFilter = document.getElementById("runtimeFilter"); + const leaderboardBody = document.getElementById("leaderboardBody"); + const sortableHeaders = document.querySelectorAll(".sortable"); + + let currentSort = { column: "rank", direction: "asc" }; + let allRows = []; + + // Initialize rows array + function initializeRows() { + allRows = Array.from(leaderboardBody.querySelectorAll("tr")).map((row) => { + return { + 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: Array.from(leaderboardBody.children).indexOf(row), + }; + }); + } + + function updateRankClasses() { + const visibleRows = 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"); + }); + } + + function calculateOverallRanking() { + const visibleRows = 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]; + + // Prevent division by zero + 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; // Use small epsilon for float comparison + + // If scores are essentially equal, prefer earlier submission + return a.timestamp - b.timestamp; + }); + + // Reorder DOM elements and update ranks + visibleRows.forEach((rowData, index) => { + leaderboardBody.appendChild(rowData.element); + }); + + updateRankClasses(); + } + + function filterLeaderboard() { + const problemTerm = (problemFilter?.value || "").toLowerCase().trim(); + const runtimeType = runtimeFilter?.value || "all"; + + // Reset all rows to visible first + allRows.forEach((rowData) => { + rowData.element.style.display = ""; + }); + + // Apply problem filter + if (problemTerm) { + 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 + 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; + + // Sort by runtime + 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"; + } + }); + }); + } + + calculateOverallRanking(); + } + + function 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 ""; + } + } + + function sortLeaderboard(column, direction) { + if (column === "rank") { + calculateOverallRanking(); + return; + } + + const visibleRows = allRows.filter( + (row) => row.element.style.display !== "none", + ); + + visibleRows.sort((a, b) => { + const valueA = getCellValue(a, column); + const valueB = 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 + visibleRows.forEach((rowData) => { + leaderboardBody.appendChild(rowData.element); + }); + + updateRankClasses(); + } + + // Event listeners for sorting + sortableHeaders.forEach((header) => { + header.addEventListener("click", () => { + const column = header.dataset.sort; + if (!column) return; + + // Remove sorting classes from all headers + sortableHeaders.forEach((h) => + h.classList.remove("sort-asc", "sort-desc"), + ); + + // Toggle sort direction + if (currentSort.column === column) { + currentSort.direction = + currentSort.direction === "asc" ? "desc" : "asc"; + } else { + currentSort.column = column; + currentSort.direction = "asc"; + } + + // Add sorting class to current header + header.classList.add(`sort-${currentSort.direction}`); + + sortLeaderboard(column, currentSort.direction); + }); + }); + + // Filter event listeners + problemFilter?.addEventListener("input", filterLeaderboard); + runtimeFilter?.addEventListener("change", filterLeaderboard); + + // Rank info popout + const rankInfoBtn = document.getElementById("rankInfoBtn"); + const rankingExplanation = document.getElementById("rankingExplanation"); + + rankInfoBtn?.addEventListener("click", (e) => { + e.preventDefault(); + rankingExplanation?.classList.toggle("active"); + rankInfoBtn?.classList.toggle("active"); + }); + + // Close ranking explanation when clicking outside + document.addEventListener("click", (e) => { + if ( + rankingExplanation?.classList.contains("active") && + !rankingExplanation.contains(e.target) && + !rankInfoBtn?.contains(e.target) + ) { + rankingExplanation.classList.remove("active"); + rankInfoBtn?.classList.remove("active"); + } + }); + + // Initialize everything + if (leaderboardBody && leaderboardBody.children.length > 0) { + initializeRows(); + calculateOverallRanking(); + + // Set initial sort indicator + const defaultHeader = document.querySelector('[data-sort="rank"]'); + if (defaultHeader) { + defaultHeader.classList.add("sort-asc"); + } + } + + // Apply dark mode to dynamically created elements + function applyDarkModeToElements() { + const isDark = html.classList.contains("dark"); + // Any additional dark mode styling for dynamically created elements can go here + } + + // Watch for dark mode changes + new MutationObserver(applyDarkModeToElements).observe(html, { + attributes: true, + attributeFilter: ["class"], + }); +}); diff --git a/src/static/style.css b/src/static/style.css index b576b4a..2724ba5 100644 --- a/src/static/style.css +++ b/src/static/style.css @@ -1,260 +1,317 @@ -/* Reset and base styles */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - line-height: 1.6; - color: #333; - background-color: #f8f9fa; - padding: 20px; - max-width: 1200px; - margin: 0 auto; -} - -/* Main heading */ -h1 { - color: #2c3e50; - margin-bottom: -10px; - padding-bottom: 3px; - border-bottom: 3px solid #3498db; - font-size: 2.2em; -} - -h2 { - color: #34495e; - margin: 30px 0 20px 0; - font-size: 1.5em; -} - -h3 { - color: #34495e; - margin: 25px 0 15px 0; - font-size: 1.3em; -} - -/* Links and buttons */ -a { - color: #3498db; - text-decoration: none; - padding: 8px 16px; - border-radius: 5px; - transition: background-color 0.3s ease; -} - -a:hover { - background-color: #e3f2fd; - text-decoration: none; -} - -/* Primary action link (Submit New Problem) */ -a[href="/problem/new"] { - background-color: #3498db; - color: white; - font-weight: 600; - margin-bottom: 30px; - display: inline-block; - padding: 12px 24px; - border-radius: 8px; -} - -a[href="/problem/new"]:hover { - background-color: #2980b9; -} - -/* Problem list */ -ul { - list-style: none; - background: white; - border-radius: 8px; - box-shadow: 0 2px 10px rgba(0,0,0,0.1); - padding: 25px; - margin: 20px 0; -} - -li { - padding: 15px 0; - border-bottom: 1px solid #eee; -} - -li:last-child { - border-bottom: none; -} - -li a { - display: block; - padding: 12px 20px; - margin: -12px -20px; - border-radius: 6px; - font-size: 1.1em; -} - -li a:hover { - background-color: #f8f9fa; - transform: translateX(5px); - transition: all 0.2s ease; -} - -/* Problem page specific styles */ -.problem-header { - display: flex; - align-items: center; - margin-bottom: 30px; - gap: 20px; -} - -.back-btn { - background-color: #95a5a6; - color: white; - border: none; - padding: 10px 20px; - border-radius: 6px; - cursor: pointer; - font-size: 14px; - font-weight: 500; - transition: background-color 0.3s ease; -} - -.back-btn:hover { - background-color: #7f8c8d; -} - -.problem-desc { - background: white; - padding: 30px; - border-radius: 8px; - box-shadow: 0 2px 10px rgba(0,0,0,0.1); - margin-bottom: 30px; - font-size: 1.1em; - line-height: 1.7; -} - -/* Editor section */ -.editor-section { - background: white; - padding: 30px; - border-radius: 8px; - box-shadow: 0 2px 10px rgba(0,0,0,0.1); - margin-bottom: 30px; -} - -#editor { - border: 2px solid #ddd; - border-radius: 8px; - margin: 20px 0; - height: 400px; - overflow: hidden; -} - -.editor-actions { - margin-top: 20px; - text-align: right; -} - -form button[type="submit"] { - background-color: #27ae60; - color: white; - border: none; - padding: 12px 30px; - border-radius: 8px; - cursor: pointer; - font-size: 16px; - font-weight: 600; - transition: background-color 0.3s ease; -} - -form button[type="submit"]:hover { - background-color: #229954; -} - -/* Results section */ -b { - color: #2c3e50; - display: inline-block; - margin: 10px 0 5px 0; -} - -pre { - background-color: #f4f4f4; - padding: 20px; - border-radius: 6px; - border-left: 4px solid #3498db; - margin: 10px 0 20px 0; - overflow-x: auto; - font-family: 'JetBrains Mono', 'Courier New', monospace; - font-size: 14px; - line-height: 1.4; -} - -pre[style*="color:red"] { - border-left-color: #e74c3c; - background-color: #fdf2f2; -} - -/* Status messages */ -p[style*="color:green"] { - background-color: #d4edda; - color: #155724; - padding: 15px 20px; - border-radius: 6px; - border-left: 4px solid #27ae60; - margin: 20px 0; - font-weight: 600; -} - -p[style*="color:red"] { - background-color: #f8d7da; - color: #721c24; - padding: 15px 20px; - border-radius: 6px; - border-left: 4px solid #e74c3c; - margin: 20px 0; - font-weight: 600; -} - -/* Back to Problems link */ -a[href="/"] { - display: inline-block; - margin-top: 30px; - background-color: #6c757d; - color: white; - padding: 10px 20px; - border-radius: 6px; - font-weight: 500; -} - -a[href="/"]:hover { - background-color: #5a6268; -} - -/* Responsive design */ -@media (max-width: 768px) { - body { - padding: 15px; - } - - .problem-header { - flex-direction: column; - align-items: flex-start; - gap: 15px; - } - - h1 { - font-size: 1.8em; - } - - .problem-desc, .editor-section, ul { - padding: 20px; - } - - #editor { - height: 300px; - } - - .editor-actions { - text-align: center; - } -} +:root { + --bg: #f8f9fa; + --card: #fff; + --text: #333; + --heading: #2c3e50; + --heading-secondary: #34495e; + --accent: #3498db; + --accent-hover: #2980b9; + --success: #27ae60; + --success-hover: #229954; + --error: #e74c3c; + --muted: #6c757d; + --muted-hover: #5a6268; + --border: #ddd; + --code-bg: #f4f4f4; + --success-bg: #d4edda; + --success-text: #155724; + --error-bg: #f8d7da; + --error-text: #721c24; + --hover-bg: #e3f2fd; + --shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +html.dark { + --bg: #0f172a; + --card: #1e293b; + --text: #f1f5f9; + --heading: #3b82f6; + --heading-secondary: #94a3b8; + --accent: #3b82f6; + --accent-hover: #2563eb; + --success: #22c55e; + --success-hover: #16a34a; + --error: #ef4444; + --muted: #64748b; + --muted-hover: #475569; + --border: #334155; + --code-bg: #1e293b; + --success-bg: #065f46; + --success-text: #d1fae5; + --error-bg: #7f1d1d; + --error-text: #fecaca; + --hover-bg: #1e40af; + --shadow: 0 2px 10px rgba(0, 0, 0, 0.3); +} + +/* Reset and base styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + line-height: 1.6; + color: var(--text); + background-color: var(--bg); + padding: 20px; + max-width: 1200px; + margin: 0 auto; + transition: + background-color 0.3s ease, + color 0.3s ease; +} + +/* Main heading */ +h1 { + color: var(--heading); + margin-bottom: -10px; + padding-bottom: 3px; + border-bottom: 3px solid var(--accent); + font-size: 2.2em; +} + +h2 { + color: var(--heading-secondary); + margin: 30px 0 20px 0; + font-size: 1.5em; +} + +h3 { + color: var(--heading-secondary); + margin: 25px 0 15px 0; + font-size: 1.3em; +} + +/* Links and buttons */ +a { + color: var(--accent); + text-decoration: none; + padding: 8px 16px; + border-radius: 5px; + transition: background-color 0.3s ease; +} + +a:hover { + background-color: var(--hover-bg); + text-decoration: none; +} + +/* Primary action link (Submit New Problem) */ +a[href="/problem/new"] { + background-color: var(--accent); + color: white; + font-weight: 600; + margin-bottom: 30px; + display: inline-block; + padding: 12px 24px; + border-radius: 8px; +} + +a[href="/problem/new"]:hover { + background-color: var(--accent-hover); +} + +/* Problem list */ +ul { + list-style: none; + background: var(--card); + border-radius: 8px; + box-shadow: var(--shadow); + padding: 25px; + margin: 20px 0; + transition: background-color 0.3s ease; +} + +li { + padding: 15px 0; + border-bottom: 1px solid var(--border); +} + +li:last-child { + border-bottom: none; +} + +li a { + display: block; + padding: 12px 20px; + margin: -12px -20px; + border-radius: 6px; + font-size: 1.1em; +} + +li a:hover { + background-color: var(--hover-bg); + transform: translateX(5px); + transition: all 0.2s ease; +} + +/* Problem page specific styles */ +.problem-header { + display: flex; + align-items: center; + margin-bottom: 30px; + gap: 20px; +} + +.back-btn { + background-color: var(--muted); + color: white; + border: none; + padding: 10px 20px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background-color 0.3s ease; +} + +.back-btn:hover { + background-color: var(--muted-hover); +} + +.problem-desc { + background: var(--card); + padding: 30px; + border-radius: 8px; + box-shadow: var(--shadow); + margin-bottom: 30px; + font-size: 1.1em; + line-height: 1.7; + transition: background-color 0.3s ease; +} + +/* Editor section */ +.editor-section { + background: var(--card); + padding: 30px; + border-radius: 8px; + box-shadow: var(--shadow); + margin-bottom: 30px; + transition: background-color 0.3s ease; +} + +#editor { + border: 2px solid var(--border); + border-radius: 8px; + margin: 20px 0; + height: 400px; + overflow: hidden; +} + +.editor-actions { + margin-top: 20px; + text-align: right; +} + +form button[type="submit"] { + background-color: var(--success); + color: white; + border: none; + padding: 12px 30px; + border-radius: 8px; + cursor: pointer; + font-size: 16px; + font-weight: 600; + transition: background-color 0.3s ease; +} + +form button[type="submit"]:hover { + background-color: var(--success-hover); +} + +/* Results section */ +b { + color: var(--heading); + display: inline-block; + margin: 10px 0 5px 0; +} + +pre { + background-color: var(--code-bg); + padding: 20px; + border-radius: 6px; + border-left: 4px solid var(--accent); + margin: 10px 0 20px 0; + overflow-x: auto; + font-family: "JetBrains Mono", "Courier New", monospace; + font-size: 14px; + line-height: 1.4; + border: 1px solid var(--border); + transition: background-color 0.3s ease; +} + +pre[style*="color:red"] { + border-left-color: var(--error); + background-color: var(--error-bg); +} + +/* Status messages */ +p[style*="color:green"] { + background-color: var(--success-bg); + color: var(--success-text); + padding: 15px 20px; + border-radius: 6px; + border-left: 4px solid var(--success); + margin: 20px 0; + font-weight: 600; +} + +p[style*="color:red"] { + background-color: var(--error-bg); + color: var(--error-text); + padding: 15px 20px; + border-radius: 6px; + border-left: 4px solid var(--error); + margin: 20px 0; + font-weight: 600; +} + +/* Back to Problems link */ +a[href="/"] { + display: inline-block; + margin-top: 30px; + background-color: var(--muted); + color: white; + padding: 10px 20px; + border-radius: 6px; + font-weight: 500; +} + +a[href="/"]:hover { + background-color: var(--muted-hover); +} + +/* Responsive design */ +@media (max-width: 768px) { + body { + padding: 15px; + } + + .problem-header { + flex-direction: column; + align-items: flex-start; + gap: 15px; + } + + h1 { + font-size: 1.8em; + } + + .problem-desc, + .editor-section, + ul { + padding: 20px; + } + + #editor { + height: 300px; + } + + .editor-actions { + text-align: center; + } +} diff --git a/src/templates/index.html b/src/templates/index.html index 14dec25..744137b 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -1,136 +1,146 @@ - - - - - -Quick Problem Platform - - - - - -
-
-

Quick Problem Platform

-
-
- -
-
- -
-

Problems

-
- {% for folder, description, test_code, difficulty in problems %} -
- {{ folder.replace('_',' ').title() }} - {{ difficulty }} -
- {% else %} -
No problems yet.
- {% endfor %} -
-
- - -
-
-

Leaderboard - ℹ️ -

-
-
- - -
-
- - - - - - - - - - - - - - {% for entry in leaderboard %} - - - - - - - - - - {% else %} - - {% endfor %} - -
RankUserProblemRuntime (s)Memory (KB)LineTimestamp
{{ loop.index }}{{ entry[0] }} - - {{ problem_titles.get(entry[1], 'Unknown') }} - - {{ '%.4f'|format(entry[2]) }}{{ entry[3] }}{{ entry[4] if entry[4] else '-' }}{{ entry[5] }}
No leaderboard entries yet.
-
-
- - -
-

How Ranking Works

-

The leaderboard uses a weighted scoring system to determine overall rank:

-
    -
  • Runtime: How fast the solution runs (lower is better).
  • -
  • Memory Usage: How much memory the solution uses (lower is better).
  • -
-

Overall score is calculated as:

-
- - runtimeScore = yourRuntime / bestRuntime
- memoryScore = yourMemory / bestMemory
- overallScore = runtimeScore × 0.7 + memoryScore × 0.3 -
-
-

Lower overall scores are better. If scores are equal, earlier submission ranks higher.

-
-
-
- - - \ No newline at end of file + + + + + +Quick Problem Platform + + + + + + +
+
+
+

Quick Problem Platform

+ +
+
+
+ +
+
+ +
+

Problems

+
+ {% for folder, description, test_code, difficulty in problems %} +
+ {{ folder.replace('_',' ').title() }} + {{ difficulty }} +
+ {% else %} +
No problems yet.
+ {% endfor %} +
+
+ + Page 1 of 1 + +
+
+ + +
+
+

Leaderboard + ℹ️ +

+
+
+ + +
+
+ + + + + + + + + + + + + + {% for entry in leaderboard %} + + + + + + + + + + {% else %} + + {% endfor %} + +
RankUserProblemRuntime (s)Memory (KB)LineTimestamp
{{ loop.index }}{{ entry[0] }} + + {{ problem_titles.get(entry[1], 'Unknown') }} + + {{ '%.4f'|format(entry[2]) }}{{ entry[3] }}{{ entry[4] if entry[4] else '-' }}{{ entry[5] }}
No leaderboard entries yet.
+
+
+ + +
+

How Ranking Works

+

The leaderboard uses a weighted scoring system to determine overall rank:

+
    +
  • Runtime: How fast the solution runs (lower is better).
  • +
  • Memory Usage: How much memory the solution uses (lower is better).
  • +
+

Overall score is calculated as:

+
+ + runtimeScore = yourRuntime / bestRuntime
+ memoryScore = yourMemory / bestMemory
+ overallScore = runtimeScore × 0.7 + memoryScore × 0.3 +
+
+

Lower overall scores are better. If scores are equal, earlier submission ranks higher.

+
+
+
+ + + diff --git a/src/templates/problem.html b/src/templates/problem.html index 20b0068..46d79ca 100644 --- a/src/templates/problem.html +++ b/src/templates/problem.html @@ -1,85 +1,158 @@ - - - - - - {{ problem.title }} - Coding Problem - - - - - - - - -
-
-
- -

{{ problem.title }}

-
-
{{ problem.description | safe | markdown }}
-
- -
-
-

Submit Your Solution (Python)

-
-
-
- - -
- -
- -
-
- -
-

Result

- {% if result %} -

Runtime: {{ '%.4f'|format(result.runtime) }} seconds

-

Output:

-
{{ result.output }}
- {% if result.error %} -

Error:

-
{{ result.error }}
- {% endif %} - {% else %} -
- Your code execution results will appear here -
- {% endif %} -
-
-
-
- - - - - \ No newline at end of file + + + + + + {{ problem.title }} - Coding Problem + + + + + + + + +
+
+
+ +

{{ problem.title }}

+ +
+
+ {{ problem.description | safe | markdown }} +
+
+ +
+
+

+ Submit Your Solution (Python) +

+
+
+
+ + +
+ +
+ +
+
+ +
+

Result

+ {% if result %} +

+ Runtime: {{ '%.4f'|format(result.runtime) }} + seconds +

+

Output:

+
{{ result.output }}
+ {% if result.error %} +

Error:

+
{{ result.error }}
+ {% endif %} {% else %} +
+ Your code execution results will appear here +
+ {% endif %} +
+
+
+
+ + + + + diff --git a/src/templates/script.js b/src/templates/script.js deleted file mode 100644 index 6106ca8..0000000 --- a/src/templates/script.js +++ /dev/null @@ -1,274 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - // Problem search - const problemSearch = document.getElementById('problemSearch'); - const problemsContainer = document.getElementById('problemsContainer'); - const problemItems = problemsContainer?.querySelectorAll('.problem-item') || []; - - problemSearch?.addEventListener('input', () => { - const term = problemSearch.value.toLowerCase().trim(); - problemItems.forEach(item => { - const name = item.dataset.name?.toLowerCase() || ''; - const desc = item.dataset.desc?.toLowerCase() || ''; - const shouldShow = !term || name.includes(term) || desc.includes(term); - item.style.display = shouldShow ? '' : 'none'; - }); - }); - - // Leaderboard functionality - const problemFilter = document.getElementById('problemFilter'); - const runtimeFilter = document.getElementById('runtimeFilter'); - const leaderboardBody = document.getElementById('leaderboardBody'); - const sortableHeaders = document.querySelectorAll('.sortable'); - - let currentSort = { column: 'rank', direction: 'asc' }; - let allRows = []; - - // Initialize rows array - function initializeRows() { - allRows = Array.from(leaderboardBody.querySelectorAll('tr')).map(row => { - return { - 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: Array.from(leaderboardBody.children).indexOf(row) - }; - }); - } - - function updateRankClasses() { - const visibleRows = 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'); - }); - } - - function calculateOverallRanking() { - const visibleRows = 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]; - - // Prevent division by zero - 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; // Use small epsilon for float comparison - - // If scores are essentially equal, prefer earlier submission - return a.timestamp - b.timestamp; - }); - - // Reorder DOM elements and update ranks - visibleRows.forEach((rowData, index) => { - leaderboardBody.appendChild(rowData.element); - }); - - updateRankClasses(); - } - - function filterLeaderboard() { - const problemTerm = (problemFilter?.value || '').toLowerCase().trim(); - const runtimeType = runtimeFilter?.value || 'all'; - - // Reset all rows to visible first - allRows.forEach(rowData => { - rowData.element.style.display = ''; - }); - - // Apply problem filter - if (problemTerm) { - 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 - 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; - - // Sort by runtime - 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'; - } - }); - }); - } - - calculateOverallRanking(); - } - - function 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 ''; - } - } - - function sortLeaderboard(column, direction) { - if (column === 'rank') { - calculateOverallRanking(); - return; - } - - const visibleRows = allRows.filter(row => row.element.style.display !== 'none'); - - visibleRows.sort((a, b) => { - const valueA = getCellValue(a, column); - const valueB = 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 - visibleRows.forEach(rowData => { - leaderboardBody.appendChild(rowData.element); - }); - - updateRankClasses(); - } - - // Event listeners for sorting - sortableHeaders.forEach(header => { - header.addEventListener('click', () => { - const column = header.dataset.sort; - if (!column) return; - - // Remove sorting classes from all headers - sortableHeaders.forEach(h => h.classList.remove('sort-asc', 'sort-desc')); - - // Toggle sort direction - if (currentSort.column === column) { - currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc'; - } else { - currentSort.column = column; - currentSort.direction = 'asc'; - } - - // Add sorting class to current header - header.classList.add(`sort-${currentSort.direction}`); - - sortLeaderboard(column, currentSort.direction); - }); - }); - - // Filter event listeners - problemFilter?.addEventListener('input', filterLeaderboard); - runtimeFilter?.addEventListener('change', filterLeaderboard); - - // Rank info popout - const rankInfoBtn = document.getElementById('rankInfoBtn'); - const rankingExplanation = document.getElementById('rankingExplanation'); - - rankInfoBtn?.addEventListener('click', (e) => { - e.preventDefault(); - rankingExplanation?.classList.toggle('active'); - rankInfoBtn?.classList.toggle('active'); - }); - - // Close ranking explanation when clicking outside - document.addEventListener('click', (e) => { - if (rankingExplanation?.classList.contains('active') && - !rankingExplanation.contains(e.target) && - !rankInfoBtn?.contains(e.target)) { - rankingExplanation.classList.remove('active'); - rankInfoBtn?.classList.remove('active'); - } - }); - - // Initialize everything - if (leaderboardBody && leaderboardBody.children.length > 0) { - initializeRows(); - calculateOverallRanking(); - - // Set initial sort indicator - const defaultHeader = document.querySelector('[data-sort="rank"]'); - if (defaultHeader) { - defaultHeader.classList.add('sort-asc'); - } - } -}); \ No newline at end of file