semi working darkmode
This commit is contained in:
@@ -1,24 +1,47 @@
|
|||||||
:root {
|
:root {
|
||||||
--bg: #f6f8fb;
|
--bg: #f6f8fb;
|
||||||
--card: #fff;
|
--card: #fff;
|
||||||
|
--text: #0f172a;
|
||||||
--muted: #6b7280;
|
--muted: #6b7280;
|
||||||
--accent: #2563eb;
|
--accent: #2563eb;
|
||||||
|
--border: #e5e7eb;
|
||||||
|
--hover: #f3f4f6;
|
||||||
--shadow: 0 4px 12px rgba(16, 24, 40, 0.06);
|
--shadow: 0 4px 12px rgba(16, 24, 40, 0.06);
|
||||||
--radius: 8px;
|
--radius: 8px;
|
||||||
--mono: 'JetBrains Mono', monospace;
|
--mono: "JetBrains Mono", monospace;
|
||||||
}
|
}
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
||||||
html, body {
|
/* 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%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
font-family: Inter, sans-serif;
|
font-family: Inter, sans-serif;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: #0f172a;
|
color: var(--text);
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
transition:
|
||||||
|
background-color 0.3s ease,
|
||||||
|
color 0.3s ease;
|
||||||
}
|
}
|
||||||
.wrap {
|
.wrap {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -27,15 +50,49 @@ body {
|
|||||||
header {
|
header {
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
}
|
}
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
header h1 {
|
header h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
color: #111827;
|
color: var(--text);
|
||||||
}
|
}
|
||||||
header p {
|
header p {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 0.9rem;
|
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 {
|
.content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
@@ -59,32 +116,53 @@ header p {
|
|||||||
.search-input {
|
.search-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid var(--border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 0.9rem;
|
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 {
|
.filter-select {
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid var(--border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
background: white;
|
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 */
|
||||||
.problems-list .problem-item {
|
.problems-list .problem-item {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border-bottom: 1px solid #e5e7eb;
|
border-bottom: 1px solid var(--border);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
.problem-item:hover {
|
||||||
|
background: var(--hover);
|
||||||
}
|
}
|
||||||
.problem-item:last-child {
|
.problem-item:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
.problem-item a {
|
.problem-item a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #0077ff;
|
color: var(--accent);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
.problem-item a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
/* Difficulty badge */
|
/* Difficulty badge */
|
||||||
.difficulty {
|
.difficulty {
|
||||||
@@ -99,14 +177,14 @@ header p {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.difficulty[data-difficulty="easy"] {
|
.difficulty[data-difficulty="easy"] {
|
||||||
background-color: #4CAF50; /* Green */
|
background-color: #4caf50; /* Green */
|
||||||
}
|
}
|
||||||
.difficulty[data-difficulty="medium"] {
|
.difficulty[data-difficulty="medium"] {
|
||||||
background-color: #FFC107; /* Amber */
|
background-color: #ffc107; /* Amber */
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
.difficulty[data-difficulty="hard"] {
|
.difficulty[data-difficulty="hard"] {
|
||||||
background-color: #F44336; /* Red */
|
background-color: #f44336; /* Red */
|
||||||
}
|
}
|
||||||
/* Leaderboard */
|
/* Leaderboard */
|
||||||
.leaderboard-head {
|
.leaderboard-head {
|
||||||
@@ -128,16 +206,16 @@ header p {
|
|||||||
.leaderboard-table th,
|
.leaderboard-table th,
|
||||||
.leaderboard-table td {
|
.leaderboard-table td {
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
border-bottom: 1px solid #e5e7eb;
|
border-bottom: 1px solid var(--border);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
.leaderboard-table th {
|
.leaderboard-table th {
|
||||||
background: #f9fafb;
|
background: var(--hover);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
.leaderboard-table tr:hover {
|
.leaderboard-table tr:hover {
|
||||||
background: #f3f4f6;
|
background: var(--hover);
|
||||||
}
|
}
|
||||||
/* Sort indicators */
|
/* Sort indicators */
|
||||||
.sortable {
|
.sortable {
|
||||||
@@ -179,7 +257,9 @@ header p {
|
|||||||
background: rgba(37, 99, 235, 0.15);
|
background: rgba(37, 99, 235, 0.15);
|
||||||
}
|
}
|
||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
.content { grid-template-columns: 1fr; }
|
.content {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
.leaderboard-controls {
|
.leaderboard-controls {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -187,7 +267,9 @@ header p {
|
|||||||
|
|
||||||
/* Leaderboard horizontal collapse */
|
/* Leaderboard horizontal collapse */
|
||||||
#leaderboardSection {
|
#leaderboardSection {
|
||||||
transition: max-width 0.35s ease, opacity 0.25s ease;
|
transition:
|
||||||
|
max-width 0.35s ease,
|
||||||
|
opacity 0.25s ease;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
@@ -205,3 +287,40 @@ header p {
|
|||||||
#rankingExplanation {
|
#rankingExplanation {
|
||||||
transition: all 0.35s ease;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,46 @@
|
|||||||
|
: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 {
|
body {
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: "Inter", sans-serif;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: #f9f9f9;
|
background-color: var(--bg);
|
||||||
color: #333;
|
color: var(--text);
|
||||||
min-height: 100vh; /* allow content to grow */
|
min-height: 100vh; /* allow content to grow */
|
||||||
overflow-y: auto; /* allow vertical scroll */
|
overflow-y: auto; /* allow vertical scroll */
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
transition:
|
||||||
|
background-color 0.3s ease,
|
||||||
|
color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
*, *::before, *::after {
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
box-sizing: inherit;
|
box-sizing: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,11 +54,12 @@ body {
|
|||||||
.problem-panel {
|
.problem-panel {
|
||||||
flex: 1 1 400px; /* grow/shrink with base 400px */
|
flex: 1 1 400px; /* grow/shrink with base 400px */
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
background: white;
|
background: var(--card);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-right: 1px solid #eaeaea;
|
border-right: 1px solid var(--border);
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-container {
|
.editor-container {
|
||||||
@@ -35,14 +67,15 @@ body {
|
|||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: white;
|
background: var(--card);
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
overflow: hidden; /* internal scroll handling */
|
overflow: hidden; /* internal scroll handling */
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-header {
|
.editor-header {
|
||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
border-bottom: 1px solid #eaeaea;
|
border-bottom: 1px solid var(--border);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,28 +91,65 @@ body {
|
|||||||
.problem-header {
|
.problem-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-btn {
|
.back-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #666;
|
color: var(--muted);
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
padding: 5px;
|
padding: 6px 10px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-btn:hover {
|
.back-btn:hover {
|
||||||
color: #000;
|
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 {
|
h1 {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #1a1a1a;
|
color: var(--text);
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.problem-desc {
|
.problem-desc {
|
||||||
@@ -89,19 +159,20 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.problem-desc pre {
|
.problem-desc pre {
|
||||||
background: #f6f8fa;
|
background: var(--code-bg);
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: "JetBrains Mono", monospace;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.problem-desc code {
|
.problem-desc code {
|
||||||
background: #f6f8fa;
|
background: var(--code-bg);
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: "JetBrains Mono", monospace;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +184,7 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.editor-actions button {
|
.editor-actions button {
|
||||||
background-color: #007bff;
|
background-color: var(--accent);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
@@ -121,16 +192,17 @@ h1 {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-actions button:hover {
|
.editor-actions button:hover {
|
||||||
background-color: #0069d9;
|
background-color: var(--accent-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
#editor {
|
#editor {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid var(--editor-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
max-height: 60vh;
|
max-height: 60vh;
|
||||||
@@ -139,12 +211,14 @@ h1 {
|
|||||||
.result-panel {
|
.result-panel {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
background: #f8f9fa;
|
background: var(--hover);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
min-height: 120px;
|
min-height: 120px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: 30vh;
|
max-height: 30vh;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-panel h3 {
|
.result-panel h3 {
|
||||||
@@ -154,18 +228,19 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.result-panel pre {
|
.result-panel pre {
|
||||||
background: #f6f8fa;
|
background: var(--code-bg);
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: "JetBrains Mono", monospace;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
|
border: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder {
|
.placeholder {
|
||||||
color: #999;
|
color: var(--muted);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@@ -175,16 +250,24 @@ label {
|
|||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #666;
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"] {
|
input[type="text"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid var(--border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
font-family: 'Inter', sans-serif;
|
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 */
|
/* Responsive adjustments */
|
||||||
@@ -194,13 +277,14 @@ input[type="text"] {
|
|||||||
height: auto;
|
height: auto;
|
||||||
overflow-y: visible;
|
overflow-y: visible;
|
||||||
}
|
}
|
||||||
.problem-panel, .editor-container {
|
.problem-panel,
|
||||||
|
.editor-container {
|
||||||
flex: none;
|
flex: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: auto;
|
min-width: auto;
|
||||||
max-height: none;
|
max-height: none;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-bottom: 1px solid #eaeaea;
|
border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
#editor {
|
#editor {
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
|
|||||||
402
src/static/script.js
Normal file
402
src/static/script.js
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
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"],
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +1,49 @@
|
|||||||
|
: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 */
|
/* Reset and base styles */
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -6,39 +52,43 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
color: #333;
|
color: var(--text);
|
||||||
background-color: #f8f9fa;
|
background-color: var(--bg);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
transition:
|
||||||
|
background-color 0.3s ease,
|
||||||
|
color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Main heading */
|
/* Main heading */
|
||||||
h1 {
|
h1 {
|
||||||
color: #2c3e50;
|
color: var(--heading);
|
||||||
margin-bottom: -10px;
|
margin-bottom: -10px;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
border-bottom: 3px solid #3498db;
|
border-bottom: 3px solid var(--accent);
|
||||||
font-size: 2.2em;
|
font-size: 2.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
color: #34495e;
|
color: var(--heading-secondary);
|
||||||
margin: 30px 0 20px 0;
|
margin: 30px 0 20px 0;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
color: #34495e;
|
color: var(--heading-secondary);
|
||||||
margin: 25px 0 15px 0;
|
margin: 25px 0 15px 0;
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Links and buttons */
|
/* Links and buttons */
|
||||||
a {
|
a {
|
||||||
color: #3498db;
|
color: var(--accent);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
@@ -46,13 +96,13 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
background-color: #e3f2fd;
|
background-color: var(--hover-bg);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Primary action link (Submit New Problem) */
|
/* Primary action link (Submit New Problem) */
|
||||||
a[href="/problem/new"] {
|
a[href="/problem/new"] {
|
||||||
background-color: #3498db;
|
background-color: var(--accent);
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
@@ -62,22 +112,23 @@ a[href="/problem/new"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a[href="/problem/new"]:hover {
|
a[href="/problem/new"]:hover {
|
||||||
background-color: #2980b9;
|
background-color: var(--accent-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Problem list */
|
/* Problem list */
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
background: white;
|
background: var(--card);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
box-shadow: var(--shadow);
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
padding: 15px 0;
|
padding: 15px 0;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
li:last-child {
|
li:last-child {
|
||||||
@@ -93,7 +144,7 @@ li a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
li a:hover {
|
li a:hover {
|
||||||
background-color: #f8f9fa;
|
background-color: var(--hover-bg);
|
||||||
transform: translateX(5px);
|
transform: translateX(5px);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
@@ -107,7 +158,7 @@ li a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.back-btn {
|
.back-btn {
|
||||||
background-color: #95a5a6;
|
background-color: var(--muted);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
@@ -119,30 +170,32 @@ li a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.back-btn:hover {
|
.back-btn:hover {
|
||||||
background-color: #7f8c8d;
|
background-color: var(--muted-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.problem-desc {
|
.problem-desc {
|
||||||
background: white;
|
background: var(--card);
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
box-shadow: var(--shadow);
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Editor section */
|
/* Editor section */
|
||||||
.editor-section {
|
.editor-section {
|
||||||
background: white;
|
background: var(--card);
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
box-shadow: var(--shadow);
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#editor {
|
#editor {
|
||||||
border: 2px solid #ddd;
|
border: 2px solid var(--border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
@@ -155,7 +208,7 @@ li a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
form button[type="submit"] {
|
form button[type="submit"] {
|
||||||
background-color: #27ae60;
|
background-color: var(--success);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 12px 30px;
|
padding: 12px 30px;
|
||||||
@@ -167,50 +220,52 @@ form button[type="submit"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
form button[type="submit"]:hover {
|
form button[type="submit"]:hover {
|
||||||
background-color: #229954;
|
background-color: var(--success-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Results section */
|
/* Results section */
|
||||||
b {
|
b {
|
||||||
color: #2c3e50;
|
color: var(--heading);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 10px 0 5px 0;
|
margin: 10px 0 5px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
background-color: #f4f4f4;
|
background-color: var(--code-bg);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border-left: 4px solid #3498db;
|
border-left: 4px solid var(--accent);
|
||||||
margin: 10px 0 20px 0;
|
margin: 10px 0 20px 0;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
font-family: 'JetBrains Mono', 'Courier New', monospace;
|
font-family: "JetBrains Mono", "Courier New", monospace;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre[style*="color:red"] {
|
pre[style*="color:red"] {
|
||||||
border-left-color: #e74c3c;
|
border-left-color: var(--error);
|
||||||
background-color: #fdf2f2;
|
background-color: var(--error-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Status messages */
|
/* Status messages */
|
||||||
p[style*="color:green"] {
|
p[style*="color:green"] {
|
||||||
background-color: #d4edda;
|
background-color: var(--success-bg);
|
||||||
color: #155724;
|
color: var(--success-text);
|
||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border-left: 4px solid #27ae60;
|
border-left: 4px solid var(--success);
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
p[style*="color:red"] {
|
p[style*="color:red"] {
|
||||||
background-color: #f8d7da;
|
background-color: var(--error-bg);
|
||||||
color: #721c24;
|
color: var(--error-text);
|
||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border-left: 4px solid #e74c3c;
|
border-left: 4px solid var(--error);
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -219,7 +274,7 @@ p[style*="color:red"] {
|
|||||||
a[href="/"] {
|
a[href="/"] {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
background-color: #6c757d;
|
background-color: var(--muted);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -227,7 +282,7 @@ a[href="/"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a[href="/"]:hover {
|
a[href="/"]:hover {
|
||||||
background-color: #5a6268;
|
background-color: var(--muted-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive design */
|
/* Responsive design */
|
||||||
@@ -246,7 +301,9 @@ a[href="/"]:hover {
|
|||||||
font-size: 1.8em;
|
font-size: 1.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.problem-desc, .editor-section, ul {
|
.problem-desc,
|
||||||
|
.editor-section,
|
||||||
|
ul {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" class="">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Quick Problem Platform</title>
|
<title>Quick Problem Platform</title>
|
||||||
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">
|
||||||
|
</head>
|
||||||
<style>
|
<style>
|
||||||
/* Popout explanation */
|
/* Popout explanation */
|
||||||
#rankingExplanation {
|
#rankingExplanation {
|
||||||
@@ -34,7 +35,12 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<header>
|
<header>
|
||||||
|
<div class="header-content">
|
||||||
<h1>Quick Problem Platform</h1>
|
<h1>Quick Problem Platform</h1>
|
||||||
|
<button id="darkModeToggle" class="dark-mode-toggle" title="Toggle dark mode">
|
||||||
|
<span class="dark-mode-icon"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="content" id="contentContainer">
|
<div class="content" id="contentContainer">
|
||||||
<!-- Problems -->
|
<!-- Problems -->
|
||||||
@@ -45,7 +51,7 @@
|
|||||||
<h2 style="margin-bottom:6px;font-size:1.1rem">Problems</h2>
|
<h2 style="margin-bottom:6px;font-size:1.1rem">Problems</h2>
|
||||||
<div id="problemsContainer">
|
<div id="problemsContainer">
|
||||||
{% for folder, description, test_code, difficulty in problems %}
|
{% for folder, description, test_code, difficulty in problems %}
|
||||||
<div class="problem-item" data-name="{{ folder.replace('_',' ').title() }}" data-desc="{{ description }}">
|
<div class="problem-item" data-name="{{ folder.replace('_',' ').title() }}" data-desc="{{ description }}" data-difficulty="{{ difficulty|lower }}">
|
||||||
<a href="/problem/{{ folder }}">{{ folder.replace('_',' ').title() }}</a>
|
<a href="/problem/{{ folder }}">{{ folder.replace('_',' ').title() }}</a>
|
||||||
<span class="difficulty" data-difficulty="{{ difficulty|lower }}">{{ difficulty }}</span>
|
<span class="difficulty" data-difficulty="{{ difficulty|lower }}">{{ difficulty }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,6 +59,11 @@
|
|||||||
<div class="problem-item">No problems yet.</div>
|
<div class="problem-item">No problems yet.</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pagination-controls" id="problemsPagination">
|
||||||
|
<button class="pagination-btn" id="problemsPrevBtn" disabled>← Previous</button>
|
||||||
|
<span class="pagination-info" id="problemsPaginationInfo">Page 1 of 1</span>
|
||||||
|
<button class="pagination-btn" id="problemsNextBtn" disabled>Next →</button>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Leaderboard -->
|
<!-- Leaderboard -->
|
||||||
@@ -131,6 +142,6 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="./script.js"></script>
|
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,36 +1,68 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" class="">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>{{ problem.title }} - Coding Problem</title>
|
<title>{{ problem.title }} - Coding Problem</title>
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css" />
|
||||||
<link rel="stylesheet" href="/static/problem.css">
|
<link rel="stylesheet" href="/static/problem.css" />
|
||||||
<!-- this is stoopid fucking html link for favicon. just cause of flask-->
|
<!-- this is stoopid fucking html link for favicon. just cause of flask-->
|
||||||
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
<link
|
||||||
<link href="https://fonts.cdnfonts.com/css/jetbrains-mono" rel="stylesheet">
|
rel="icon"
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
type="image/x-icon"
|
||||||
</head>
|
href="{{ url_for('static', filename='favicon.ico') }}"
|
||||||
<body>
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.cdnfonts.com/css/jetbrains-mono"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
<div class="problem-panel">
|
<div class="problem-panel">
|
||||||
<div class="problem-header">
|
<div class="problem-header">
|
||||||
<button class="back-btn" onclick="window.location.href='/'">← Back</button>
|
<button class="back-btn" onclick="window.location.href='/'">
|
||||||
|
← Back
|
||||||
|
</button>
|
||||||
<h1>{{ problem.title }}</h1>
|
<h1>{{ problem.title }}</h1>
|
||||||
|
<button
|
||||||
|
id="darkModeToggle"
|
||||||
|
class="dark-mode-toggle"
|
||||||
|
title="Toggle dark mode"
|
||||||
|
>
|
||||||
|
<span class="dark-mode-icon"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="problem-desc">
|
||||||
|
{{ problem.description | safe | markdown }}
|
||||||
</div>
|
</div>
|
||||||
<div class="problem-desc">{{ problem.description | safe | markdown }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="editor-container">
|
<div class="editor-container">
|
||||||
<div class="editor-header">
|
<div class="editor-header">
|
||||||
<h2 style="margin:0;font-size:18px;">Submit Your Solution (Python)</h2>
|
<h2 style="margin: 0; font-size: 18px">
|
||||||
|
Submit Your Solution (Python)
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-wrapper">
|
<div class="editor-wrapper">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<label for="username">Username (optional):</label>
|
<label for="username">Username (optional):</label>
|
||||||
<input type="text" name="username" id="username" placeholder="Anonymous">
|
<input
|
||||||
|
type="text"
|
||||||
|
name="username"
|
||||||
|
id="username"
|
||||||
|
placeholder="Anonymous"
|
||||||
|
/>
|
||||||
<div id="editor"></div>
|
<div id="editor"></div>
|
||||||
<textarea name="user_code" id="user_code" style="display:none;"></textarea>
|
<textarea
|
||||||
|
name="user_code"
|
||||||
|
id="user_code"
|
||||||
|
style="display: none"
|
||||||
|
></textarea>
|
||||||
<div class="editor-actions">
|
<div class="editor-actions">
|
||||||
<button type="submit">Run & Submit</button>
|
<button type="submit">Run & Submit</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,14 +71,16 @@
|
|||||||
<div class="result-panel">
|
<div class="result-panel">
|
||||||
<h3>Result</h3>
|
<h3>Result</h3>
|
||||||
{% if result %}
|
{% if result %}
|
||||||
<p><b>Runtime:</b> {{ '%.4f'|format(result.runtime) }} seconds</p>
|
<p>
|
||||||
|
<b>Runtime:</b> {{ '%.4f'|format(result.runtime) }}
|
||||||
|
seconds
|
||||||
|
</p>
|
||||||
<p><b>Output:</b></p>
|
<p><b>Output:</b></p>
|
||||||
<pre>{{ result.output }}</pre>
|
<pre>{{ result.output }}</pre>
|
||||||
{% if result.error %}
|
{% if result.error %}
|
||||||
<p><b>Error:</b></p>
|
<p><b>Error:</b></p>
|
||||||
<pre>{{ result.error }}</pre>
|
<pre>{{ result.error }}</pre>
|
||||||
{% endif %}
|
{% endif %} {% else %}
|
||||||
{% else %}
|
|
||||||
<div class="placeholder">
|
<div class="placeholder">
|
||||||
Your code execution results will appear here
|
Your code execution results will appear here
|
||||||
</div>
|
</div>
|
||||||
@@ -58,28 +92,67 @@
|
|||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js"></script>
|
||||||
<script>
|
<script>
|
||||||
require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs' } });
|
// Dark mode functionality
|
||||||
require(['vs/editor/editor.main'], function() {
|
const darkModeToggle = document.getElementById("darkModeToggle");
|
||||||
var editor = monaco.editor.create(document.getElementById('editor'), {
|
const html = document.documentElement;
|
||||||
value: '',
|
|
||||||
language: 'python',
|
// Load saved dark mode preference
|
||||||
theme: 'vs-light',
|
const savedDarkMode = localStorage.getItem("darkMode");
|
||||||
fontFamily: 'JetBrains Mono, monospace',
|
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"),
|
||||||
|
);
|
||||||
|
// Update Monaco editor theme
|
||||||
|
if (window.monacoEditor) {
|
||||||
|
const isDark = html.classList.contains("dark");
|
||||||
|
window.monacoEditor.updateOptions({
|
||||||
|
theme: isDark ? "vs-dark" : "vs-light",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
require.config({
|
||||||
|
paths: {
|
||||||
|
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
require(["vs/editor/editor.main"], function () {
|
||||||
|
const isDark = html.classList.contains("dark");
|
||||||
|
window.monacoEditor = monaco.editor.create(
|
||||||
|
document.getElementById("editor"),
|
||||||
|
{
|
||||||
|
value: "",
|
||||||
|
language: "python",
|
||||||
|
theme: isDark ? "vs-dark" : "vs-light",
|
||||||
|
fontFamily: "JetBrains Mono, monospace",
|
||||||
fontLigatures: true,
|
fontLigatures: true,
|
||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
minimap: { enabled: false }
|
minimap: { enabled: false },
|
||||||
});
|
},
|
||||||
document.querySelector('form').addEventListener('submit', function(e) {
|
);
|
||||||
var code = editor.getValue();
|
document
|
||||||
|
.querySelector("form")
|
||||||
|
.addEventListener("submit", function (e) {
|
||||||
|
var code = window.monacoEditor.getValue();
|
||||||
if (!code.trim()) {
|
if (!code.trim()) {
|
||||||
alert('Please enter your code before submitting.');
|
alert("Please enter your code before submitting.");
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
document.getElementById('user_code').value = code;
|
document.getElementById("user_code").value = code;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user