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 ad1ee83..0ebf5fa 100644
--- a/src/static/index.css
+++ b/src/static/index.css
@@ -1,326 +1,326 @@
-: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;
-}
+: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 ca509b5..83037b4 100644
--- a/src/static/problem.css
+++ b/src/static/problem.css
@@ -1,296 +1,296 @@
-: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;
- }
-}
+: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
index 7d151de..38d271f 100644
--- a/src/static/script.js
+++ b/src/static/script.js
@@ -1,402 +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 &&
- 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 = 10;
-
- // 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"],
- });
-});
+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 1bed033..2724ba5 100644
--- a/src/static/style.css
+++ b/src/static/style.css
@@ -1,317 +1,317 @@
-: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;
- }
-}
+: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 0bd6cf8..744137b 100644
--- a/src/templates/index.html
+++ b/src/templates/index.html
@@ -1,147 +1,146 @@
-
-
-
-
-
-Quick Problem Platform
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Problems
-
- {% for folder, description, test_code, difficulty in problems %}
-
- {% else %}
-
No problems yet.
- {% endfor %}
-
-
-
-
-
-
-
-
Leaderboard
- âšī¸
-
-
-
-
-
-
-
-
-
-
- | Rank |
- User |
- Problem |
- Runtime (s) |
- Memory (KB) |
- Line |
- Timestamp |
-
-
-
- {% for entry in leaderboard %}
-
- | {{ loop.index }} |
- {{ entry[0] }} |
-
-
- {{ problem_titles.get(entry[1], 'Unknown') }}
-
- |
- {{ '%.4f'|format(entry[2]) }} |
- {{ entry[3] }} |
- {{ entry[4] if entry[4] else '-' }} |
- {{ entry[5] }} |
-
- {% else %}
- | No leaderboard entries yet. |
- {% endfor %}
-
-
-
-
-
-
-
- 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.
-
-
-
-
-
-