this works now, ironed out shit and made stuff work

This commit is contained in:
2025-08-17 11:47:56 +02:00
parent 57a7b0e68f
commit 5dc45b9a9b
8 changed files with 1664 additions and 1663 deletions

View File

@@ -1,17 +1,17 @@
#!/bin/bash u!/bin/bash
set -e # exit if any command fails set -e # exit if any command fails
# Ensure QPP/database directory exists # Ensure QPP/database directory exists
mkdir -p src/database mkdir -p src/database
python -m venv venv python -m venv venv
source venv/bin/activate source venv/bin/activate
pip install --upgrade pip pip install --upgrade pip
pip install -r requirements.txt pip install -r requirements.txt
export FLASK_APP=src.app export FLASK_APP=src.app
export FLASK_ENV=production export FLASK_ENV=production
flask run --host=0.0.0.0 --port=5000 flask run --host=0.0.0.0 --port=5000

1
run.bat Normal file
View File

@@ -0,0 +1 @@
python -m flask --app .\src\app.py run --host=0.0.0.0 --port=5000

View File

@@ -1,326 +1,326 @@
:root { :root {
--bg: #f6f8fb; --bg: #f6f8fb;
--card: #fff; --card: #fff;
--text: #0f172a; --text: #0f172a;
--muted: #6b7280; --muted: #6b7280;
--accent: #2563eb; --accent: #2563eb;
--border: #e5e7eb; --border: #e5e7eb;
--hover: #f3f4f6; --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;
} }
/* Dark mode variables */ /* Dark mode variables */
html.dark { html.dark {
--bg: #0f172a; --bg: #0f172a;
--card: #1e293b; --card: #1e293b;
--text: #f1f5f9; --text: #f1f5f9;
--muted: #94a3b8; --muted: #94a3b8;
--accent: #3b82f6; --accent: #3b82f6;
--border: #334155; --border: #334155;
--hover: #334155; --hover: #334155;
--shadow: 0 4px 12px rgba(0, 0, 0, 0.3); --shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
} }
* { * {
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
html, html,
body { body {
height: 100%; height: 100%;
} }
body { body {
font-family: Inter, sans-serif; font-family: Inter, sans-serif;
background: var(--bg); background: var(--bg);
color: var(--text); color: var(--text);
padding: 16px; padding: 16px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
transition: transition:
background-color 0.3s ease, background-color 0.3s ease,
color 0.3s ease; color 0.3s ease;
} }
.wrap { .wrap {
width: 100%; width: 100%;
max-width: 1100px; max-width: 1100px;
} }
header { header {
margin-bottom: 14px; margin-bottom: 14px;
} }
.header-content { .header-content {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
position: relative; position: relative;
} }
header h1 { header h1 {
text-align: center; text-align: center;
font-size: 1.6rem; font-size: 1.6rem;
color: var(--text); color: var(--text);
} }
header p { header p {
color: var(--muted); color: var(--muted);
font-size: 0.9rem; font-size: 0.9rem;
} }
.dark-mode-toggle { .dark-mode-toggle {
position: absolute; position: absolute;
right: 0; right: 0;
background: none; background: none;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius); border-radius: var(--radius);
padding: 6px 10px; padding: 6px 10px;
cursor: pointer; cursor: pointer;
color: var(--text); color: var(--text);
font-size: 1.2rem; font-size: 1.2rem;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.dark-mode-toggle:hover { .dark-mode-toggle:hover {
background: var(--hover); background: var(--hover);
transform: scale(1.05); transform: scale(1.05);
} }
html.dark .dark-mode-icon::before { html.dark .dark-mode-icon::before {
content: "☀️"; content: "☀";
} }
html:not(.dark) .dark-mode-icon::before { html:not(.dark) .dark-mode-icon::before {
content: "🌙"; content: "";
} }
.dark-mode-icon { .dark-mode-icon {
display: inline-block; display: inline-block;
} }
.dark-mode-icon::before { .dark-mode-icon::before {
font-size: 1em; font-size: 1em;
} }
.content { .content {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: 12px; gap: 12px;
} }
.content.single-column { .content.single-column {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.card { .card {
background: var(--card); background: var(--card);
border-radius: var(--radius); border-radius: var(--radius);
box-shadow: var(--shadow); box-shadow: var(--shadow);
padding: 12px; padding: 12px;
} }
/* Search/filter controls */ /* Search/filter controls */
.search-controls { .search-controls {
margin-bottom: 12px; margin-bottom: 12px;
display: flex; display: flex;
gap: 8px; gap: 8px;
} }
.search-input { .search-input {
flex: 1; flex: 1;
padding: 6px 10px; padding: 6px 10px;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 4px; border-radius: 4px;
font-size: 0.9rem; font-size: 0.9rem;
background: var(--card); background: var(--card);
color: var(--text); color: var(--text);
transition: border-color 0.3s ease; transition: border-color 0.3s ease;
} }
.search-input:focus { .search-input:focus {
outline: none; outline: none;
border-color: var(--accent); border-color: var(--accent);
} }
.filter-select { .filter-select {
padding: 6px 8px; padding: 6px 8px;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 4px; border-radius: 4px;
font-size: 0.9rem; font-size: 0.9rem;
background: var(--card); background: var(--card);
color: var(--text); color: var(--text);
transition: border-color 0.3s ease; transition: border-color 0.3s ease;
} }
.filter-select:focus { .filter-select:focus {
outline: none; outline: none;
border-color: var(--accent); border-color: var(--accent);
} }
/* Problems list */ /* Problems list */
.problems-list .problem-item { .problems-list .problem-item {
padding: 8px; padding: 8px;
border-bottom: 1px solid var(--border); 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; transition: background-color 0.3s ease;
} }
.problem-item:hover { .problem-item:hover {
background: var(--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: var(--accent); color: var(--accent);
font-weight: 600; font-weight: 600;
transition: color 0.3s ease; transition: color 0.3s ease;
} }
.problem-item a:hover { .problem-item a:hover {
text-decoration: underline; text-decoration: underline;
} }
/* Difficulty badge */ /* Difficulty badge */
.difficulty { .difficulty {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
padding: 0.25em 0.6em; padding: 0.25em 0.6em;
border-radius: 10px; border-radius: 10px;
font-size: 0.85em; font-size: 0.85em;
font-weight: bold; font-weight: bold;
text-transform: uppercase; text-transform: uppercase;
color: white; color: white;
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 {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 6px; margin-bottom: 6px;
} }
.leaderboard-controls { .leaderboard-controls {
display: flex; display: flex;
gap: 8px; gap: 8px;
margin-bottom: 12px; margin-bottom: 12px;
} }
.leaderboard-table { .leaderboard-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
font-size: 0.9rem; font-size: 0.9rem;
} }
.leaderboard-table th, .leaderboard-table th,
.leaderboard-table td { .leaderboard-table td {
padding: 6px 8px; padding: 6px 8px;
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
text-align: left; text-align: left;
} }
.leaderboard-table th { .leaderboard-table th {
background: var(--hover); background: var(--hover);
font-weight: 600; font-weight: 600;
color: var(--muted); color: var(--muted);
} }
.leaderboard-table tr:hover { .leaderboard-table tr:hover {
background: var(--hover); background: var(--hover);
} }
/* Sort indicators */ /* Sort indicators */
.sortable { .sortable {
cursor: pointer; cursor: pointer;
position: relative; position: relative;
padding-right: 16px; padding-right: 16px;
} }
.sortable::after { .sortable::after {
content: "↕"; content: "↕";
position: absolute; position: absolute;
right: 4px; right: 4px;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
font-size: 0.8em; font-size: 0.8em;
opacity: 0.5; opacity: 0.5;
} }
.sort-asc::after { .sort-asc::after {
content: "↑"; content: "↑";
opacity: 1; opacity: 1;
} }
.sort-desc::after { .sort-desc::after {
content: "↓"; content: "↓";
opacity: 1; opacity: 1;
} }
/* Toggle button */ /* Toggle button */
.btn { .btn {
border: none; border: none;
background: transparent; background: transparent;
cursor: pointer; cursor: pointer;
color: var(--accent); color: var(--accent);
font-size: 0.85rem; font-size: 0.85rem;
padding: 4px 6px; padding: 4px 6px;
border-radius: 4px; border-radius: 4px;
} }
.btn:hover { .btn:hover {
background: rgba(37, 99, 235, 0.08); background: rgba(37, 99, 235, 0.08);
} }
.btn.active { .btn.active {
background: rgba(37, 99, 235, 0.15); background: rgba(37, 99, 235, 0.15);
} }
@media (max-width: 800px) { @media (max-width: 800px) {
.content { .content {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.leaderboard-controls { .leaderboard-controls {
flex-direction: column; flex-direction: column;
} }
} }
/* Leaderboard horizontal collapse */ /* Leaderboard horizontal collapse */
#leaderboardSection { #leaderboardSection {
transition: transition:
max-width 0.35s ease, max-width 0.35s ease,
opacity 0.25s ease; opacity 0.25s ease;
overflow: hidden; overflow: hidden;
max-width: 100%; max-width: 100%;
} }
#leaderboardSection.hidden { #leaderboardSection.hidden {
max-width: 0; max-width: 0;
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
} }
#leaderboardSection.visible { #leaderboardSection.visible {
max-width: 100%; /* take full available space in grid column */ max-width: 100%; /* take full available space in grid column */
opacity: 1; opacity: 1;
} }
#rankingExplanation { #rankingExplanation {
transition: all 0.35s ease; transition: all 0.35s ease;
} }
/* Pagination Controls */ /* Pagination Controls */
.pagination-controls { .pagination-controls {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-top: 12px; margin-top: 12px;
padding-top: 12px; padding-top: 12px;
border-top: 1px solid var(--border); border-top: 1px solid var(--border);
} }
.pagination-btn { .pagination-btn {
background: var(--card); background: var(--card);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 4px; border-radius: 4px;
padding: 6px 12px; padding: 6px 12px;
cursor: pointer; cursor: pointer;
color: var(--text); color: var(--text);
font-size: 0.9rem; font-size: 0.9rem;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.pagination-btn:hover:not(:disabled) { .pagination-btn:hover:not(:disabled) {
background: var(--hover); background: var(--hover);
border-color: var(--accent); border-color: var(--accent);
} }
.pagination-btn:disabled { .pagination-btn:disabled {
opacity: 0.5; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;
} }
.pagination-info { .pagination-info {
color: var(--muted); color: var(--muted);
font-size: 0.9rem; font-size: 0.9rem;
} }
/* Hide pagination when not needed */ /* Hide pagination when not needed */
.pagination-controls.hidden { .pagination-controls.hidden {
display: none; display: none;
} }

View File

@@ -1,296 +1,296 @@
:root { :root {
--bg: #f9f9f9; --bg: #f9f9f9;
--card: #fff; --card: #fff;
--text: #333; --text: #333;
--muted: #666; --muted: #666;
--accent: #007bff; --accent: #007bff;
--accent-hover: #0069d9; --accent-hover: #0069d9;
--border: #eaeaea; --border: #eaeaea;
--hover: #f8f9fa; --hover: #f8f9fa;
--code-bg: #f6f8fa; --code-bg: #f6f8fa;
--editor-border: #ddd; --editor-border: #ddd;
} }
html.dark { html.dark {
--bg: #0f172a; --bg: #0f172a;
--card: #1e293b; --card: #1e293b;
--text: #f1f5f9; --text: #f1f5f9;
--muted: #94a3b8; --muted: #94a3b8;
--accent: #3b82f6; --accent: #3b82f6;
--accent-hover: #2563eb; --accent-hover: #2563eb;
--border: #334155; --border: #334155;
--hover: #334155; --hover: #334155;
--code-bg: #1e293b; --code-bg: #1e293b;
--editor-border: #475569; --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: var(--bg); background-color: var(--bg);
color: var(--text); 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: transition:
background-color 0.3s ease, background-color 0.3s ease,
color 0.3s ease; color 0.3s ease;
} }
*, *,
*::before, *::before,
*::after { *::after {
box-sizing: inherit; box-sizing: inherit;
} }
.main-container { .main-container {
display: flex; display: flex;
flex-wrap: wrap; /* wrap on small screens */ flex-wrap: wrap; /* wrap on small screens */
min-height: 100vh; min-height: 100vh;
width: 100vw; width: 100vw;
} }
.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: var(--card); background: var(--card);
overflow-y: auto; overflow-y: auto;
padding: 20px; padding: 20px;
border-right: 1px solid var(--border); border-right: 1px solid var(--border);
max-height: 100vh; max-height: 100vh;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
.editor-container { .editor-container {
flex: 1 1 400px; flex: 1 1 400px;
min-width: 300px; min-width: 300px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: var(--card); 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; transition: background-color 0.3s ease;
} }
.editor-header { .editor-header {
padding: 15px 20px; padding: 15px 20px;
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
flex-shrink: 0; flex-shrink: 0;
} }
.editor-wrapper { .editor-wrapper {
flex: 1 1 auto; flex: 1 1 auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; min-height: 0;
padding: 0 20px; padding: 0 20px;
overflow-y: auto; overflow-y: auto;
} }
.problem-header { .problem-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: 20px; margin-bottom: 20px;
} }
.back-btn { .back-btn {
background: none; background: none;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 16px;
color: var(--muted); color: var(--muted);
margin-right: 15px; margin-right: 15px;
padding: 6px 10px; padding: 6px 10px;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.back-btn:hover { .back-btn:hover {
color: var(--text); color: var(--text);
background: var(--hover); background: var(--hover);
} }
.dark-mode-toggle { .dark-mode-toggle {
background: none; background: none;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 4px; border-radius: 4px;
padding: 6px 10px; padding: 6px 10px;
cursor: pointer; cursor: pointer;
color: var(--text); color: var(--text);
font-size: 1.2rem; font-size: 1.2rem;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.dark-mode-toggle:hover { .dark-mode-toggle:hover {
background: var(--hover); background: var(--hover);
transform: scale(1.05); transform: scale(1.05);
} }
html.dark .dark-mode-icon::before { html.dark .dark-mode-icon::before {
content: "☀"; content: "☀";
} }
html:not(.dark) .dark-mode-icon::before { html:not(.dark) .dark-mode-icon::before {
content: "🌙"; content: "";
} }
.dark-mode-icon { .dark-mode-icon {
display: inline-block; display: inline-block;
} }
.dark-mode-icon::before { .dark-mode-icon::before {
font-size: 1em; font-size: 1em;
} }
h1 { h1 {
font-size: 22px; font-size: 22px;
font-weight: 600; font-weight: 600;
margin: 0; margin: 0;
color: var(--text); color: var(--text);
flex: 1; flex: 1;
} }
.problem-desc { .problem-desc {
line-height: 1.6; line-height: 1.6;
font-size: 15px; font-size: 15px;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
.problem-desc pre { .problem-desc pre {
background: var(--code-bg); 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); border: 1px solid var(--border);
} }
.problem-desc code { .problem-desc code {
background: var(--code-bg); 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;
} }
.editor-actions { .editor-actions {
padding: 15px 0; padding: 15px 0;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
flex-shrink: 0; flex-shrink: 0;
} }
.editor-actions button { .editor-actions button {
background-color: var(--accent); background-color: var(--accent);
color: white; color: white;
border: none; border: none;
padding: 8px 16px; padding: 8px 16px;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-weight: 500; font-weight: 500;
font-size: 14px; font-size: 14px;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
.editor-actions button:hover { .editor-actions button:hover {
background-color: var(--accent-hover); background-color: var(--accent-hover);
} }
#editor { #editor {
flex: 1 1 auto; flex: 1 1 auto;
min-height: 300px; min-height: 300px;
border: 1px solid var(--editor-border); border: 1px solid var(--editor-border);
border-radius: 4px; border-radius: 4px;
overflow: auto; overflow: auto;
max-height: 60vh; max-height: 60vh;
} }
.result-panel { .result-panel {
margin-top: 20px; margin-top: 20px;
padding: 15px; padding: 15px;
background: var(--hover); 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); border: 1px solid var(--border);
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
.result-panel h3 { .result-panel h3 {
margin-top: 0; margin-top: 0;
font-size: 16px; font-size: 16px;
margin-bottom: 10px; margin-bottom: 10px;
} }
.result-panel pre { .result-panel pre {
background: var(--code-bg); 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); border: 1px solid var(--border);
} }
.placeholder { .placeholder {
color: var(--muted); color: var(--muted);
font-style: italic; font-style: italic;
text-align: center; text-align: center;
padding: 20px; padding: 20px;
} }
label { label {
display: block; display: block;
margin-bottom: 5px; margin-bottom: 5px;
font-size: 14px; font-size: 14px;
color: var(--muted); color: var(--muted);
} }
input[type="text"] { input[type="text"] {
width: 100%; width: 100%;
padding: 8px; padding: 8px;
border: 1px solid var(--border); 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); background: var(--card);
color: var(--text); color: var(--text);
transition: border-color 0.3s ease; transition: border-color 0.3s ease;
} }
input[type="text"]:focus { input[type="text"]:focus {
outline: none; outline: none;
border-color: var(--accent); border-color: var(--accent);
} }
/* Responsive adjustments */ /* Responsive adjustments */
@media (max-width: 768px) { @media (max-width: 768px) {
.main-container { .main-container {
flex-direction: column; flex-direction: column;
height: auto; height: auto;
overflow-y: visible; overflow-y: visible;
} }
.problem-panel, .problem-panel,
.editor-container { .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 var(--border); border-bottom: 1px solid var(--border);
} }
#editor { #editor {
min-height: 400px; min-height: 400px;
max-height: none; max-height: none;
} }
.result-panel { .result-panel {
max-height: none; max-height: none;
} }
} }

View File

@@ -1,402 +1,403 @@
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
// Dark mode functionality // Dark mode functionality
const darkModeToggle = document.getElementById("darkModeToggle"); const darkModeToggle = document.getElementById("darkModeToggle");
const html = document.documentElement; const html = document.documentElement;
// Load saved dark mode preference // Load saved dark mode preference
const savedDarkMode = localStorage.getItem("darkMode"); const savedDarkMode = localStorage.getItem("darkMode");
if ( if (
savedDarkMode === "true" || savedDarkMode === "true" ||
(savedDarkMode === null && (savedDarkMode === null &&
window.matchMedia("(prefers-color-scheme: dark)").matches) // 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"); ) {
} html.classList.add("dark");
}
darkModeToggle?.addEventListener("click", () => {
html.classList.toggle("dark"); darkModeToggle?.addEventListener("click", () => {
localStorage.setItem("darkMode", html.classList.contains("dark")); html.classList.toggle("dark");
}); localStorage.setItem("darkMode", html.classList.contains("dark"));
});
// Problem search and pagination
const problemSearch = document.getElementById("problemSearch"); // Problem search and pagination
const problemsContainer = document.getElementById("problemsContainer"); const problemSearch = document.getElementById("problemSearch");
const problemsPagination = document.getElementById("problemsPagination"); const problemsContainer = document.getElementById("problemsContainer");
const problemsPrevBtn = document.getElementById("problemsPrevBtn"); const problemsPagination = document.getElementById("problemsPagination");
const problemsNextBtn = document.getElementById("problemsNextBtn"); const problemsPrevBtn = document.getElementById("problemsPrevBtn");
const problemsPaginationInfo = document.getElementById( const problemsNextBtn = document.getElementById("problemsNextBtn");
"problemsPaginationInfo", const problemsPaginationInfo = document.getElementById(
); "problemsPaginationInfo",
);
let allProblemItems = [];
let filteredProblemItems = []; let allProblemItems = [];
let currentPage = 1; let filteredProblemItems = [];
const itemsPerPage = 10; let currentPage = 1;
const itemsPerPage = 5;
// Initialize problem items
function initializeProblemItems() { // Initialize problem items
allProblemItems = Array.from( function initializeProblemItems() {
problemsContainer?.querySelectorAll(".problem-item") || [], allProblemItems = Array.from(
); problemsContainer?.querySelectorAll(".problem-item") || [],
filteredProblemItems = [...allProblemItems]; );
updatePagination(); filteredProblemItems = [...allProblemItems];
} updatePagination();
}
function updatePagination() {
const totalPages = Math.ceil(filteredProblemItems.length / itemsPerPage); function updatePagination() {
const startIndex = (currentPage - 1) * itemsPerPage; const totalPages = Math.ceil(filteredProblemItems.length / itemsPerPage);
const endIndex = startIndex + itemsPerPage; const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
// Hide all items first
allProblemItems.forEach((item) => { // Hide all items first
item.style.display = "none"; allProblemItems.forEach((item) => {
}); item.style.display = "none";
});
// Show current page items
filteredProblemItems.slice(startIndex, endIndex).forEach((item) => { // Show current page items
item.style.display = ""; filteredProblemItems.slice(startIndex, endIndex).forEach((item) => {
}); item.style.display = "";
});
// Update pagination controls
if (problemsPrevBtn) problemsPrevBtn.disabled = currentPage <= 1; // Update pagination controls
if (problemsNextBtn) problemsNextBtn.disabled = currentPage >= totalPages; if (problemsPrevBtn) problemsPrevBtn.disabled = currentPage <= 1;
if (problemsPaginationInfo) { if (problemsNextBtn) problemsNextBtn.disabled = currentPage >= totalPages;
problemsPaginationInfo.textContent = if (problemsPaginationInfo) {
totalPages > 0 problemsPaginationInfo.textContent =
? `Page ${currentPage} of ${totalPages}` totalPages > 0
: "No problems found"; ? `Page ${currentPage} of ${totalPages}`
} : "No problems found";
}
// Hide pagination if not needed
if (problemsPagination) { // Hide pagination if not needed
problemsPagination.classList.toggle("hidden", totalPages <= 1); if (problemsPagination) {
} problemsPagination.classList.toggle("hidden", totalPages <= 1);
} }
}
function filterProblems() {
const term = problemSearch?.value.toLowerCase().trim() || ""; function filterProblems() {
filteredProblemItems = allProblemItems.filter((item) => { const term = problemSearch?.value.toLowerCase().trim() || "";
const name = item.dataset.name?.toLowerCase() || ""; filteredProblemItems = allProblemItems.filter((item) => {
const desc = item.dataset.desc?.toLowerCase() || ""; const name = item.dataset.name?.toLowerCase() || "";
return !term || name.includes(term) || desc.includes(term); const desc = item.dataset.desc?.toLowerCase() || "";
}); return !term || name.includes(term) || desc.includes(term);
currentPage = 1; });
updatePagination(); currentPage = 1;
} updatePagination();
}
// Event listeners for pagination
problemsPrevBtn?.addEventListener("click", () => { // Event listeners for pagination
if (currentPage > 1) { problemsPrevBtn?.addEventListener("click", () => {
currentPage--; if (currentPage > 1) {
updatePagination(); currentPage--;
} updatePagination();
}); }
});
problemsNextBtn?.addEventListener("click", () => {
const totalPages = Math.ceil(filteredProblemItems.length / itemsPerPage); problemsNextBtn?.addEventListener("click", () => {
if (currentPage < totalPages) { const totalPages = Math.ceil(filteredProblemItems.length / itemsPerPage);
currentPage++; if (currentPage < totalPages) {
updatePagination(); currentPage++;
} updatePagination();
}); }
});
problemSearch?.addEventListener("input", filterProblems);
problemSearch?.addEventListener("input", filterProblems);
// Initialize problems pagination
if (problemsContainer) { // Initialize problems pagination
initializeProblemItems(); if (problemsContainer) {
} initializeProblemItems();
}
// Leaderboard functionality
const problemFilter = document.getElementById("problemFilter"); // Leaderboard functionality
const runtimeFilter = document.getElementById("runtimeFilter"); const problemFilter = document.getElementById("problemFilter");
const leaderboardBody = document.getElementById("leaderboardBody"); const runtimeFilter = document.getElementById("runtimeFilter");
const sortableHeaders = document.querySelectorAll(".sortable"); const leaderboardBody = document.getElementById("leaderboardBody");
const sortableHeaders = document.querySelectorAll(".sortable");
let currentSort = { column: "rank", direction: "asc" };
let allRows = []; let currentSort = { column: "rank", direction: "asc" };
let allRows = [];
// Initialize rows array
function initializeRows() { // Initialize rows array
allRows = Array.from(leaderboardBody.querySelectorAll("tr")).map((row) => { function initializeRows() {
return { allRows = Array.from(leaderboardBody.querySelectorAll("tr")).map((row) => {
element: row, return {
user: row.dataset.user || "", element: row,
problem: row.dataset.problem || "", user: row.dataset.user || "",
runtime: parseFloat(row.dataset.runtime) || 0, problem: row.dataset.problem || "",
memory: parseFloat(row.dataset.memory) || 0, runtime: parseFloat(row.dataset.runtime) || 0,
timestamp: new Date(row.dataset.timestamp || Date.now()).getTime(), memory: parseFloat(row.dataset.memory) || 0,
language: row.dataset.language || "", timestamp: new Date(row.dataset.timestamp || Date.now()).getTime(),
originalIndex: Array.from(leaderboardBody.children).indexOf(row), language: row.dataset.language || "",
}; originalIndex: Array.from(leaderboardBody.children).indexOf(row),
}); };
} });
}
function updateRankClasses() {
const visibleRows = allRows.filter( function updateRankClasses() {
(row) => row.element.style.display !== "none", const visibleRows = allRows.filter(
); (row) => row.element.style.display !== "none",
visibleRows.forEach((rowData, index) => { );
const rank = index + 1; visibleRows.forEach((rowData, index) => {
const row = rowData.element; const rank = index + 1;
const row = rowData.element;
// Update rank cell
const rankCell = row.cells[0]; // Update rank cell
if (rankCell) rankCell.textContent = rank; const rankCell = row.cells[0];
if (rankCell) rankCell.textContent = rank;
// Update rank classes
row.className = row.className.replace(/\brank-\d+\b/g, ""); // Update rank classes
if (rank === 1) row.classList.add("rank-1"); row.className = row.className.replace(/\brank-\d+\b/g, "");
else if (rank <= 3) row.classList.add("rank-top3"); if (rank === 1) row.classList.add("rank-1");
}); else if (rank <= 3) row.classList.add("rank-top3");
} });
}
function calculateOverallRanking() {
const visibleRows = allRows.filter( function calculateOverallRanking() {
(row) => row.element.style.display !== "none", const visibleRows = allRows.filter(
); (row) => row.element.style.display !== "none",
);
if (visibleRows.length === 0) return;
if (visibleRows.length === 0) return;
// Group submissions by problem to find the best performance for each
const problemBests = {}; // Group submissions by problem to find the best performance for each
const problemBests = {};
visibleRows.forEach((rowData) => {
const problem = rowData.problem; visibleRows.forEach((rowData) => {
if (!problemBests[problem]) { const problem = rowData.problem;
problemBests[problem] = { if (!problemBests[problem]) {
bestRuntime: Infinity, problemBests[problem] = {
bestMemory: Infinity, bestRuntime: Infinity,
}; bestMemory: Infinity,
} };
}
problemBests[problem].bestRuntime = Math.min(
problemBests[problem].bestRuntime, problemBests[problem].bestRuntime = Math.min(
rowData.runtime, problemBests[problem].bestRuntime,
); rowData.runtime,
problemBests[problem].bestMemory = Math.min( );
problemBests[problem].bestMemory, problemBests[problem].bestMemory = Math.min(
rowData.memory, problemBests[problem].bestMemory,
); rowData.memory,
}); );
});
// Calculate normalized scores for each submission
visibleRows.forEach((rowData) => { // Calculate normalized scores for each submission
const problemBest = problemBests[rowData.problem]; visibleRows.forEach((rowData) => {
const problemBest = problemBests[rowData.problem];
// Prevent division by zero
const runtimeScore = // Prevent division by zero
problemBest.bestRuntime > 0 const runtimeScore =
? rowData.runtime / problemBest.bestRuntime problemBest.bestRuntime > 0
: 1; ? rowData.runtime / problemBest.bestRuntime
const memoryScore = : 1;
problemBest.bestMemory > 0 const memoryScore =
? rowData.memory / problemBest.bestMemory problemBest.bestMemory > 0
: 1; ? rowData.memory / problemBest.bestMemory
: 1;
// Weighted overall score (70% runtime, 30% memory)
rowData.overallScore = runtimeScore * 0.7 + memoryScore * 0.3; // 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) => { // Sort by overall score (lower is better), then by timestamp (earlier is better for ties)
const scoreDiff = a.overallScore - b.overallScore; visibleRows.sort((a, b) => {
if (Math.abs(scoreDiff) > 0.000001) return scoreDiff; // Use small epsilon for float comparison 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; // If scores are essentially equal, prefer earlier submission
}); return a.timestamp - b.timestamp;
});
// Reorder DOM elements and update ranks
visibleRows.forEach((rowData, index) => { // Reorder DOM elements and update ranks
leaderboardBody.appendChild(rowData.element); visibleRows.forEach((rowData, index) => {
}); leaderboardBody.appendChild(rowData.element);
});
updateRankClasses();
} updateRankClasses();
}
function filterLeaderboard() {
const problemTerm = (problemFilter?.value || "").toLowerCase().trim(); function filterLeaderboard() {
const runtimeType = runtimeFilter?.value || "all"; const problemTerm = (problemFilter?.value || "").toLowerCase().trim();
const runtimeType = runtimeFilter?.value || "all";
// Reset all rows to visible first
allRows.forEach((rowData) => { // Reset all rows to visible first
rowData.element.style.display = ""; allRows.forEach((rowData) => {
}); rowData.element.style.display = "";
});
// Apply problem filter
if (problemTerm) { // Apply problem filter
allRows.forEach((rowData) => { if (problemTerm) {
const problemMatch = rowData.problem allRows.forEach((rowData) => {
.toLowerCase() const problemMatch = rowData.problem
.includes(problemTerm); .toLowerCase()
if (!problemMatch) { .includes(problemTerm);
rowData.element.style.display = "none"; if (!problemMatch) {
} rowData.element.style.display = "none";
}); }
} });
}
// Apply runtime filter (best/worst per user per problem)
if (runtimeType === "best" || runtimeType === "worst") { // Apply runtime filter (best/worst per user per problem)
const userProblemGroups = {}; if (runtimeType === "best" || runtimeType === "worst") {
const userProblemGroups = {};
// Group by user + problem combination
allRows.forEach((rowData) => { // Group by user + problem combination
if (rowData.element.style.display === "none") return; allRows.forEach((rowData) => {
if (rowData.element.style.display === "none") return;
const key = `${rowData.user}::${rowData.problem}`;
if (!userProblemGroups[key]) { const key = `${rowData.user}::${rowData.problem}`;
userProblemGroups[key] = []; if (!userProblemGroups[key]) {
} userProblemGroups[key] = [];
userProblemGroups[key].push(rowData); }
}); userProblemGroups[key].push(rowData);
});
// Hide all except best/worst for each user-problem combination
Object.values(userProblemGroups).forEach((group) => { // Hide all except best/worst for each user-problem combination
if (group.length <= 1) return; Object.values(userProblemGroups).forEach((group) => {
if (group.length <= 1) return;
// Sort by runtime
group.sort((a, b) => a.runtime - b.runtime); // Sort by runtime
group.sort((a, b) => a.runtime - b.runtime);
const keepIndex = runtimeType === "best" ? 0 : group.length - 1;
group.forEach((rowData, index) => { const keepIndex = runtimeType === "best" ? 0 : group.length - 1;
if (index !== keepIndex) { group.forEach((rowData, index) => {
rowData.element.style.display = "none"; if (index !== keepIndex) {
} rowData.element.style.display = "none";
}); }
}); });
} });
}
calculateOverallRanking();
} calculateOverallRanking();
}
function getCellValue(rowData, column) {
switch (column) { function getCellValue(rowData, column) {
case "rank": switch (column) {
return parseInt(rowData.element.cells[0]?.textContent) || 0; case "rank":
case "user": return parseInt(rowData.element.cells[0]?.textContent) || 0;
return rowData.user.toLowerCase(); case "user":
case "problem": return rowData.user.toLowerCase();
return rowData.problem.toLowerCase(); case "problem":
case "runtime": return rowData.problem.toLowerCase();
return rowData.runtime; case "runtime":
case "memory": return rowData.runtime;
return rowData.memory; case "memory":
case "timestamp": return rowData.memory;
return rowData.timestamp; case "timestamp":
case "language": return rowData.timestamp;
return rowData.language.toLowerCase(); case "language":
default: return rowData.language.toLowerCase();
return ""; default:
} return "";
} }
}
function sortLeaderboard(column, direction) {
if (column === "rank") { function sortLeaderboard(column, direction) {
calculateOverallRanking(); if (column === "rank") {
return; calculateOverallRanking();
} return;
}
const visibleRows = allRows.filter(
(row) => row.element.style.display !== "none", const visibleRows = allRows.filter(
); (row) => row.element.style.display !== "none",
);
visibleRows.sort((a, b) => {
const valueA = getCellValue(a, column); visibleRows.sort((a, b) => {
const valueB = getCellValue(b, column); const valueA = getCellValue(a, column);
const valueB = getCellValue(b, column);
let comparison = 0;
if (typeof valueA === "number" && typeof valueB === "number") { let comparison = 0;
comparison = valueA - valueB; if (typeof valueA === "number" && typeof valueB === "number") {
} else { comparison = valueA - valueB;
comparison = valueA < valueB ? -1 : valueA > valueB ? 1 : 0; } else {
} comparison = valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
}
return direction === "asc" ? comparison : -comparison;
}); return direction === "asc" ? comparison : -comparison;
});
// Reorder DOM elements
visibleRows.forEach((rowData) => { // Reorder DOM elements
leaderboardBody.appendChild(rowData.element); visibleRows.forEach((rowData) => {
}); leaderboardBody.appendChild(rowData.element);
});
updateRankClasses();
} updateRankClasses();
}
// Event listeners for sorting
sortableHeaders.forEach((header) => { // Event listeners for sorting
header.addEventListener("click", () => { sortableHeaders.forEach((header) => {
const column = header.dataset.sort; header.addEventListener("click", () => {
if (!column) return; const column = header.dataset.sort;
if (!column) return;
// Remove sorting classes from all headers
sortableHeaders.forEach((h) => // Remove sorting classes from all headers
h.classList.remove("sort-asc", "sort-desc"), sortableHeaders.forEach((h) =>
); h.classList.remove("sort-asc", "sort-desc"),
);
// Toggle sort direction
if (currentSort.column === column) { // Toggle sort direction
currentSort.direction = if (currentSort.column === column) {
currentSort.direction === "asc" ? "desc" : "asc"; currentSort.direction =
} else { currentSort.direction === "asc" ? "desc" : "asc";
currentSort.column = column; } else {
currentSort.direction = "asc"; currentSort.column = column;
} currentSort.direction = "asc";
}
// Add sorting class to current header
header.classList.add(`sort-${currentSort.direction}`); // Add sorting class to current header
header.classList.add(`sort-${currentSort.direction}`);
sortLeaderboard(column, currentSort.direction);
}); sortLeaderboard(column, currentSort.direction);
}); });
});
// Filter event listeners
problemFilter?.addEventListener("input", filterLeaderboard); // Filter event listeners
runtimeFilter?.addEventListener("change", filterLeaderboard); problemFilter?.addEventListener("input", filterLeaderboard);
runtimeFilter?.addEventListener("change", filterLeaderboard);
// Rank info popout
const rankInfoBtn = document.getElementById("rankInfoBtn"); // Rank info popout
const rankingExplanation = document.getElementById("rankingExplanation"); const rankInfoBtn = document.getElementById("rankInfoBtn");
const rankingExplanation = document.getElementById("rankingExplanation");
rankInfoBtn?.addEventListener("click", (e) => {
e.preventDefault(); rankInfoBtn?.addEventListener("click", (e) => {
rankingExplanation?.classList.toggle("active"); e.preventDefault();
rankInfoBtn?.classList.toggle("active"); rankingExplanation?.classList.toggle("active");
}); rankInfoBtn?.classList.toggle("active");
});
// Close ranking explanation when clicking outside
document.addEventListener("click", (e) => { // Close ranking explanation when clicking outside
if ( document.addEventListener("click", (e) => {
rankingExplanation?.classList.contains("active") && if (
!rankingExplanation.contains(e.target) && rankingExplanation?.classList.contains("active") &&
!rankInfoBtn?.contains(e.target) !rankingExplanation.contains(e.target) &&
) { !rankInfoBtn?.contains(e.target)
rankingExplanation.classList.remove("active"); ) {
rankInfoBtn?.classList.remove("active"); rankingExplanation.classList.remove("active");
} rankInfoBtn?.classList.remove("active");
}); }
});
// Initialize everything
if (leaderboardBody && leaderboardBody.children.length > 0) { // Initialize everything
initializeRows(); if (leaderboardBody && leaderboardBody.children.length > 0) {
calculateOverallRanking(); initializeRows();
calculateOverallRanking();
// Set initial sort indicator
const defaultHeader = document.querySelector('[data-sort="rank"]'); // Set initial sort indicator
if (defaultHeader) { const defaultHeader = document.querySelector('[data-sort="rank"]');
defaultHeader.classList.add("sort-asc"); if (defaultHeader) {
} defaultHeader.classList.add("sort-asc");
} }
}
// Apply dark mode to dynamically created elements
function applyDarkModeToElements() { // Apply dark mode to dynamically created elements
const isDark = html.classList.contains("dark"); function applyDarkModeToElements() {
// Any additional dark mode styling for dynamically created elements can go here 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, { // Watch for dark mode changes
attributes: true, new MutationObserver(applyDarkModeToElements).observe(html, {
attributeFilter: ["class"], attributes: true,
}); attributeFilter: ["class"],
}); });
});

View File

@@ -1,317 +1,317 @@
:root { :root {
--bg: #f8f9fa; --bg: #f8f9fa;
--card: #fff; --card: #fff;
--text: #333; --text: #333;
--heading: #2c3e50; --heading: #2c3e50;
--heading-secondary: #34495e; --heading-secondary: #34495e;
--accent: #3498db; --accent: #3498db;
--accent-hover: #2980b9; --accent-hover: #2980b9;
--success: #27ae60; --success: #27ae60;
--success-hover: #229954; --success-hover: #229954;
--error: #e74c3c; --error: #e74c3c;
--muted: #6c757d; --muted: #6c757d;
--muted-hover: #5a6268; --muted-hover: #5a6268;
--border: #ddd; --border: #ddd;
--code-bg: #f4f4f4; --code-bg: #f4f4f4;
--success-bg: #d4edda; --success-bg: #d4edda;
--success-text: #155724; --success-text: #155724;
--error-bg: #f8d7da; --error-bg: #f8d7da;
--error-text: #721c24; --error-text: #721c24;
--hover-bg: #e3f2fd; --hover-bg: #e3f2fd;
--shadow: 0 2px 10px rgba(0, 0, 0, 0.1); --shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
} }
html.dark { html.dark {
--bg: #0f172a; --bg: #0f172a;
--card: #1e293b; --card: #1e293b;
--text: #f1f5f9; --text: #f1f5f9;
--heading: #3b82f6; --heading: #3b82f6;
--heading-secondary: #94a3b8; --heading-secondary: #94a3b8;
--accent: #3b82f6; --accent: #3b82f6;
--accent-hover: #2563eb; --accent-hover: #2563eb;
--success: #22c55e; --success: #22c55e;
--success-hover: #16a34a; --success-hover: #16a34a;
--error: #ef4444; --error: #ef4444;
--muted: #64748b; --muted: #64748b;
--muted-hover: #475569; --muted-hover: #475569;
--border: #334155; --border: #334155;
--code-bg: #1e293b; --code-bg: #1e293b;
--success-bg: #065f46; --success-bg: #065f46;
--success-text: #d1fae5; --success-text: #d1fae5;
--error-bg: #7f1d1d; --error-bg: #7f1d1d;
--error-text: #fecaca; --error-text: #fecaca;
--hover-bg: #1e40af; --hover-bg: #1e40af;
--shadow: 0 2px 10px rgba(0, 0, 0, 0.3); --shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
} }
/* Reset and base styles */ /* Reset and base styles */
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
} }
body { body {
font-family: font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
line-height: 1.6; line-height: 1.6;
color: var(--text); color: var(--text);
background-color: var(--bg); background-color: var(--bg);
padding: 20px; padding: 20px;
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 0 auto;
transition: transition:
background-color 0.3s ease, background-color 0.3s ease,
color 0.3s ease; color 0.3s ease;
} }
/* Main heading */ /* Main heading */
h1 { h1 {
color: var(--heading); color: var(--heading);
margin-bottom: -10px; margin-bottom: -10px;
padding-bottom: 3px; padding-bottom: 3px;
border-bottom: 3px solid var(--accent); border-bottom: 3px solid var(--accent);
font-size: 2.2em; font-size: 2.2em;
} }
h2 { h2 {
color: var(--heading-secondary); 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: var(--heading-secondary); 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: var(--accent); color: var(--accent);
text-decoration: none; text-decoration: none;
padding: 8px 16px; padding: 8px 16px;
border-radius: 5px; border-radius: 5px;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
a:hover { a:hover {
background-color: var(--hover-bg); 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: var(--accent); background-color: var(--accent);
color: white; color: white;
font-weight: 600; font-weight: 600;
margin-bottom: 30px; margin-bottom: 30px;
display: inline-block; display: inline-block;
padding: 12px 24px; padding: 12px 24px;
border-radius: 8px; border-radius: 8px;
} }
a[href="/problem/new"]:hover { a[href="/problem/new"]:hover {
background-color: var(--accent-hover); background-color: var(--accent-hover);
} }
/* Problem list */ /* Problem list */
ul { ul {
list-style: none; list-style: none;
background: var(--card); background: var(--card);
border-radius: 8px; border-radius: 8px;
box-shadow: var(--shadow); box-shadow: var(--shadow);
padding: 25px; padding: 25px;
margin: 20px 0; margin: 20px 0;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
li { li {
padding: 15px 0; padding: 15px 0;
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
} }
li:last-child { li:last-child {
border-bottom: none; border-bottom: none;
} }
li a { li a {
display: block; display: block;
padding: 12px 20px; padding: 12px 20px;
margin: -12px -20px; margin: -12px -20px;
border-radius: 6px; border-radius: 6px;
font-size: 1.1em; font-size: 1.1em;
} }
li a:hover { li a:hover {
background-color: var(--hover-bg); background-color: var(--hover-bg);
transform: translateX(5px); transform: translateX(5px);
transition: all 0.2s ease; transition: all 0.2s ease;
} }
/* Problem page specific styles */ /* Problem page specific styles */
.problem-header { .problem-header {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 30px; margin-bottom: 30px;
gap: 20px; gap: 20px;
} }
.back-btn { .back-btn {
background-color: var(--muted); background-color: var(--muted);
color: white; color: white;
border: none; border: none;
padding: 10px 20px; padding: 10px 20px;
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
.back-btn:hover { .back-btn:hover {
background-color: var(--muted-hover); background-color: var(--muted-hover);
} }
.problem-desc { .problem-desc {
background: var(--card); background: var(--card);
padding: 30px; padding: 30px;
border-radius: 8px; border-radius: 8px;
box-shadow: var(--shadow); 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; transition: background-color 0.3s ease;
} }
/* Editor section */ /* Editor section */
.editor-section { .editor-section {
background: var(--card); background: var(--card);
padding: 30px; padding: 30px;
border-radius: 8px; border-radius: 8px;
box-shadow: var(--shadow); box-shadow: var(--shadow);
margin-bottom: 30px; margin-bottom: 30px;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
#editor { #editor {
border: 2px solid var(--border); border: 2px solid var(--border);
border-radius: 8px; border-radius: 8px;
margin: 20px 0; margin: 20px 0;
height: 400px; height: 400px;
overflow: hidden; overflow: hidden;
} }
.editor-actions { .editor-actions {
margin-top: 20px; margin-top: 20px;
text-align: right; text-align: right;
} }
form button[type="submit"] { form button[type="submit"] {
background-color: var(--success); background-color: var(--success);
color: white; color: white;
border: none; border: none;
padding: 12px 30px; padding: 12px 30px;
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
form button[type="submit"]:hover { form button[type="submit"]:hover {
background-color: var(--success-hover); background-color: var(--success-hover);
} }
/* Results section */ /* Results section */
b { b {
color: var(--heading); color: var(--heading);
display: inline-block; display: inline-block;
margin: 10px 0 5px 0; margin: 10px 0 5px 0;
} }
pre { pre {
background-color: var(--code-bg); background-color: var(--code-bg);
padding: 20px; padding: 20px;
border-radius: 6px; border-radius: 6px;
border-left: 4px solid var(--accent); 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); border: 1px solid var(--border);
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
pre[style*="color:red"] { pre[style*="color:red"] {
border-left-color: var(--error); border-left-color: var(--error);
background-color: var(--error-bg); background-color: var(--error-bg);
} }
/* Status messages */ /* Status messages */
p[style*="color:green"] { p[style*="color:green"] {
background-color: var(--success-bg); background-color: var(--success-bg);
color: var(--success-text); color: var(--success-text);
padding: 15px 20px; padding: 15px 20px;
border-radius: 6px; border-radius: 6px;
border-left: 4px solid var(--success); 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: var(--error-bg); background-color: var(--error-bg);
color: var(--error-text); color: var(--error-text);
padding: 15px 20px; padding: 15px 20px;
border-radius: 6px; border-radius: 6px;
border-left: 4px solid var(--error); border-left: 4px solid var(--error);
margin: 20px 0; margin: 20px 0;
font-weight: 600; font-weight: 600;
} }
/* Back to Problems link */ /* Back to Problems link */
a[href="/"] { a[href="/"] {
display: inline-block; display: inline-block;
margin-top: 30px; margin-top: 30px;
background-color: var(--muted); background-color: var(--muted);
color: white; color: white;
padding: 10px 20px; padding: 10px 20px;
border-radius: 6px; border-radius: 6px;
font-weight: 500; font-weight: 500;
} }
a[href="/"]:hover { a[href="/"]:hover {
background-color: var(--muted-hover); background-color: var(--muted-hover);
} }
/* Responsive design */ /* Responsive design */
@media (max-width: 768px) { @media (max-width: 768px) {
body { body {
padding: 15px; padding: 15px;
} }
.problem-header { .problem-header {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
gap: 15px; gap: 15px;
} }
h1 { h1 {
font-size: 1.8em; font-size: 1.8em;
} }
.problem-desc, .problem-desc,
.editor-section, .editor-section,
ul { ul {
padding: 20px; padding: 20px;
} }
#editor { #editor {
height: 300px; height: 300px;
} }
.editor-actions { .editor-actions {
text-align: center; text-align: center;
} }
} }

View File

@@ -1,147 +1,146 @@
<!doctype html> <!doctype html>
<html lang="en" class=""> <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> </head>
<style> <style>
/* Popout explanation */ /* Popout explanation */
#rankingExplanation { #rankingExplanation {
grid-column: 1 / span 2; grid-column: 1 / span 2;
max-height: 0; max-height: 0;
opacity: 0; opacity: 0;
overflow: hidden; overflow: hidden;
transition: max-height 0.5s ease, opacity 0.4s ease, padding 0.4s ease; transition: max-height 0.5s ease, opacity 0.4s ease, padding 0.4s ease;
padding: 0 12px; padding: 0 12px;
} }
#rankingExplanation.active { #rankingExplanation.active {
max-height: 800px; max-height: 800px;
opacity: 1; opacity: 1;
padding: 12px; padding: 12px;
} }
#rankInfoBtn.active { color: #2563eb; cursor:pointer; transition: transform 0.3s ease; } #rankInfoBtn.active { color: #2563eb; cursor:pointer; transition: transform 0.3s ease; }
#rankInfoBtn.active { transform: rotate(90deg); } #rankInfoBtn.active { transform: rotate(90deg); }
/* Highlight top rank */ /* Highlight top rank */
.rank-1 { background-color: #f0fff0; } .rank-1 td:first-child { font-weight: bold; }
.rank-1 td:first-child { font-weight: bold; color: #2e7d32; } .sort-asc::after { content: " ↑"; }
.sort-asc::after { content: " "; } .sort-desc::after { content: " "; }
.sort-desc::after { content: " ↓"; } </style>
</style> </head>
</head> <body>
<body> <div class="wrap">
<div class="wrap"> <header>
<header> <div class="header-content">
<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">
<button id="darkModeToggle" class="dark-mode-toggle" title="Toggle dark mode"> <span class="dark-mode-icon"></span>
<span class="dark-mode-icon"></span> </button>
</button> </div>
</div> </header>
</header> <div class="content" id="contentContainer">
<div class="content" id="contentContainer"> <!-- Problems -->
<!-- Problems --> <section class="card problems-list">
<section class="card problems-list"> <div class="search-controls">
<div class="search-controls"> <input type="text" class="search-input" id="problemSearch" placeholder="Search problems..." />
<input type="text" class="search-input" id="problemSearch" placeholder="Search problems..." /> </div>
</div> <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 }}" data-difficulty="{{ difficulty|lower }}">
<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> {% else %}
{% else %} <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">
<div class="pagination-controls" id="problemsPagination"> <button class="pagination-btn" id="problemsPrevBtn" disabled>← Previous</button>
<button class="pagination-btn" id="problemsPrevBtn" disabled>← Previous</button> <span class="pagination-info" id="problemsPaginationInfo">Page 1 of 1</span>
<span class="pagination-info" id="problemsPaginationInfo">Page 1 of 1</span> <button class="pagination-btn" id="problemsNextBtn" disabled>Next →</button>
<button class="pagination-btn" id="problemsNextBtn" disabled>Next →</button> </div>
</div> </section>
</section>
<!-- Leaderboard -->
<!-- Leaderboard --> <section class="card" id="leaderboardSection">
<section class="card" id="leaderboardSection"> <div class="leaderboard-head">
<div class="leaderboard-head"> <h2 style="font-size:1.1rem;margin:0">Leaderboard
<h2 style="font-size:1.1rem;margin:0">Leaderboard <span id="rankInfoBtn" title="How ranking works"></span>
<span id="rankInfoBtn" title="How ranking works"></span> </h2>
</h2> </div>
</div> <div class="leaderboard-controls">
<div class="leaderboard-controls"> <input type="text" class="search-input" id="problemFilter" placeholder="Filter by problem..." />
<input type="text" class="search-input" id="problemFilter" placeholder="Filter by problem..." /> <select class="filter-select" id="runtimeFilter">
<select class="filter-select" id="runtimeFilter"> <option value="">All runtimes</option>
<option value="">All runtimes</option> <option value="best">Best runtime</option>
<option value="best">Best runtime</option> <option value="worst">Worst runtime</option>
<option value="worst">Worst runtime</option> </select>
</select> </div>
</div> <div id="leaderboardContainer">
<div id="leaderboardContainer"> <table class="leaderboard-table">
<table class="leaderboard-table"> <thead>
<thead> <tr>
<tr> <th class="sortable" data-sort="rank">Rank</th>
<th class="sortable" data-sort="rank">Rank</th> <th class="sortable" data-sort="user">User</th>
<th class="sortable" data-sort="user">User</th> <th class="sortable" data-sort="problem">Problem</th>
<th class="sortable" data-sort="problem">Problem</th> <th class="sortable" data-sort="runtime">Runtime (s)</th>
<th class="sortable" data-sort="runtime">Runtime (s)</th> <th class="sortable" data-sort="memory">Memory (KB)</th>
<th class="sortable" data-sort="memory">Memory (KB)</th> <th>Line</th>
<th>Line</th> <th class="sortable" data-sort="timestamp">Timestamp</th>
<th class="sortable" data-sort="timestamp">Timestamp</th> </tr>
</tr> </thead>
</thead> <tbody id="leaderboardBody">
<tbody id="leaderboardBody"> {% for entry in leaderboard %}
{% for entry in leaderboard %} <tr data-user="{{ entry[0] }}" data-problem="{{ problem_titles.get(entry[1], 'Unknown') }}"
<tr data-user="{{ entry[0] }}" data-problem="{{ problem_titles.get(entry[1], 'Unknown') }}" data-runtime="{{ '%.4f'|format(entry[2]) }}" data-memory="{{ entry[3] }}"
data-runtime="{{ '%.4f'|format(entry[2]) }}" data-memory="{{ entry[3] }}" data-timestamp="{{ entry[5] }}">
data-timestamp="{{ entry[5] }}"> <td>{{ loop.index }}</td>
<td>{{ loop.index }}</td> <td>{{ entry[0] }}</td>
<td>{{ entry[0] }}</td> <td>
<td> <a href="/problem/{{ problem_titles.get(entry[1], 'Unknown') }}"
<a href="/problem/{{ problem_titles.get(entry[1], 'Unknown') }}" style="color:#2563eb; text-decoration: none;"
style="color:#2563eb; text-decoration: none;" onmouseover="this.style.textDecoration='underline';"
onmouseover="this.style.textDecoration='underline';" onmouseout="this.style.textDecoration='none';">
onmouseout="this.style.textDecoration='none';"> {{ problem_titles.get(entry[1], 'Unknown') }}
{{ problem_titles.get(entry[1], 'Unknown') }} </a>
</a> </td>
</td> <td>{{ '%.4f'|format(entry[2]) }}</td>
<td>{{ '%.4f'|format(entry[2]) }}</td> <td>{{ entry[3] }}</td>
<td>{{ entry[3] }}</td> <td>{{ entry[4] if entry[4] else '-' }}</td>
<td>{{ entry[4] if entry[4] else '-' }}</td> <td>{{ entry[5] }}</td>
<td>{{ entry[5] }}</td> </tr>
</tr> {% else %}
{% else %} <tr><td colspan="7">No leaderboard entries yet.</td></tr>
<tr><td colspan="7">No leaderboard entries yet.</td></tr> {% endfor %}
{% endfor %} </tbody>
</tbody> </table>
</table> </div>
</div> </section>
</section>
<!-- Ranking explanation -->
<!-- Ranking explanation --> <section class="card" id="rankingExplanation">
<section class="card" id="rankingExplanation"> <h2 style="font-size:1.1rem;margin-bottom:6px">How Ranking Works</h2>
<h2 style="font-size:1.1rem;margin-bottom:6px">How Ranking Works</h2> <p>The leaderboard uses a <strong>weighted scoring system</strong> to determine overall rank:</p>
<p>The leaderboard uses a <strong>weighted scoring system</strong> to determine overall rank:</p> <ul style="margin-left: 15px;">
<ul style="margin-left: 15px;"> <li><strong>Runtime:</strong> How fast the solution runs (lower is better).</li>
<li><strong>Runtime:</strong> How fast the solution runs (lower is better).</li> <li><strong>Memory Usage:</strong> How much memory the solution uses (lower is better).</li>
<li><strong>Memory Usage:</strong> How much memory the solution uses (lower is better).</li> </ul>
</ul> <p>Overall score is calculated as:</p>
<p>Overall score is calculated as:</p> <div style="background:#f9f9f9; border-left:4px solid #007acc; padding:1rem; margin:1rem 0;">
<div style="background:#f9f9f9; border-left:4px solid #007acc; padding:1rem; margin:1rem 0;"> <code>
<code> runtimeScore = yourRuntime / bestRuntime<br>
runtimeScore = yourRuntime / bestRuntime<br> memoryScore = yourMemory / bestMemory<br>
memoryScore = yourMemory / bestMemory<br> overallScore = runtimeScore × 0.7 + memoryScore × 0.3
overallScore = runtimeScore × 0.7 + memoryScore × 0.3 </code>
</code> </div>
</div> <p>Lower overall scores are better. If scores are equal, earlier submission ranks higher.</p>
<p>Lower overall scores are better. If scores are equal, earlier submission ranks higher.</p> </section>
</section> </div>
</div> </div>
</div> <script src="{{ url_for('static', filename='script.js') }}"></script>
<script src="{{ url_for('static', filename='script.js') }}"></script> </body>
</body> </html>
</html>

View File

@@ -1,158 +1,158 @@
<!doctype html> <!doctype html>
<html lang="en" class=""> <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 <link
rel="icon" rel="icon"
type="image/x-icon" type="image/x-icon"
href="{{ url_for('static', filename='favicon.ico') }}" href="{{ url_for('static', filename='favicon.ico') }}"
/> />
<link <link
href="https://fonts.cdnfonts.com/css/jetbrains-mono" href="https://fonts.cdnfonts.com/css/jetbrains-mono"
rel="stylesheet" rel="stylesheet"
/> />
<link <link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
rel="stylesheet" rel="stylesheet"
/> />
</head> </head>
<body> <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='/'"> <button class="back-btn" onclick="window.location.href='/'">
← Back ← Back
</button> </button>
<h1>{{ problem.title }}</h1> <h1>{{ problem.title }}</h1>
<button <button
id="darkModeToggle" id="darkModeToggle"
class="dark-mode-toggle" class="dark-mode-toggle"
title="Toggle dark mode" title="Toggle dark mode"
> >
<span class="dark-mode-icon"></span> <span class="dark-mode-icon"></span>
</button> </button>
</div> </div>
<div class="problem-desc"> <div class="problem-desc">
{{ problem.description | safe | markdown }} {{ problem.description | safe | markdown }}
</div> </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"> <h2 style="margin: 0; font-size: 18px">
Submit Your Solution (Python) Submit Your Solution (Python)
</h2> </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 <input
type="text" type="text"
name="username" name="username"
id="username" id="username"
placeholder="Anonymous" placeholder="Anonymous"
/> />
<div id="editor"></div> <div id="editor"></div>
<textarea <textarea
name="user_code" name="user_code"
id="user_code" id="user_code"
style="display: none" style="display: none"
></textarea> ></textarea>
<div class="editor-actions"> <div class="editor-actions">
<button type="submit">Run & Submit</button> <button type="submit">Run & Submit</button>
</div> </div>
</form> </form>
<div class="result-panel"> <div class="result-panel">
<h3>Result</h3> <h3>Result</h3>
{% if result %} {% if result %}
<p> <p>
<b>Runtime:</b> {{ '%.4f'|format(result.runtime) }} <b>Runtime:</b> {{ '%.4f'|format(result.runtime) }}
seconds seconds
</p> </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 %} {% else %} {% endif %} {% else %}
<div class="placeholder"> <div class="placeholder">
Your code execution results will appear here Your code execution results will appear here
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<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>
// Dark mode functionality // Dark mode functionality
const darkModeToggle = document.getElementById("darkModeToggle"); const darkModeToggle = document.getElementById("darkModeToggle");
const html = document.documentElement; const html = document.documentElement;
// Load saved dark mode preference // Load saved dark mode preference
const savedDarkMode = localStorage.getItem("darkMode"); const savedDarkMode = localStorage.getItem("darkMode");
if ( if (
savedDarkMode === "true" || savedDarkMode === "true" ||
(savedDarkMode === null && (savedDarkMode === null &&
window.matchMedia("(prefers-color-scheme: dark)").matches) window.matchMedia("(prefers-color-scheme: dark)").matches)
) { ) {
html.classList.add("dark"); html.classList.add("dark");
} }
darkModeToggle.addEventListener("click", () => { darkModeToggle.addEventListener("click", () => {
html.classList.toggle("dark"); html.classList.toggle("dark");
localStorage.setItem( localStorage.setItem(
"darkMode", "darkMode",
html.classList.contains("dark"), html.classList.contains("dark"),
); );
// Update Monaco editor theme // Update Monaco editor theme
if (window.monacoEditor) { if (window.monacoEditor) {
const isDark = html.classList.contains("dark"); const isDark = html.classList.contains("dark");
window.monacoEditor.updateOptions({ window.monacoEditor.updateOptions({
theme: isDark ? "vs-dark" : "vs-light", theme: isDark ? "vs-dark" : "vs-light",
}); });
} }
}); });
require.config({ require.config({
paths: { paths: {
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs", vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs",
}, },
}); });
require(["vs/editor/editor.main"], function () { require(["vs/editor/editor.main"], function () {
const isDark = html.classList.contains("dark"); const isDark = html.classList.contains("dark");
window.monacoEditor = monaco.editor.create( window.monacoEditor = monaco.editor.create(
document.getElementById("editor"), document.getElementById("editor"),
{ {
value: "", value: "",
language: "python", language: "python",
theme: isDark ? "vs-dark" : "vs-light", theme: isDark ? "vs-dark" : "vs-light",
fontFamily: "JetBrains Mono, monospace", fontFamily: "JetBrains Mono, monospace",
fontLigatures: true, fontLigatures: true,
automaticLayout: true, automaticLayout: true,
fontSize: 16, fontSize: 16,
minimap: { enabled: false }, minimap: { enabled: false },
}, },
); );
document document
.querySelector("form") .querySelector("form")
.addEventListener("submit", function (e) { .addEventListener("submit", function (e) {
var code = window.monacoEditor.getValue(); 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>