Skip to content

Commit e25ce94

Browse files
committed
localize scoreboard templates and introduce centralized text constants
1 parent 025665c commit e25ce94

10 files changed

Lines changed: 331 additions & 118 deletions

File tree

scoreboard/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Scoreboard
22

3-
Static HTML generator for the course scoreboard. All calculations happen in Python; the output pages are
4-
offline-ready and contain no JavaScript.
3+
Static HTML generator for the course scoreboard. All calculations happen in Python; a small bundled JS
4+
snippet is used only for client-side variant calculation.
55

66
## Usage
77

@@ -29,4 +29,4 @@ pytest -v
2929

3030
- Threads deadlines are auto-distributed across 1 Feb → 15 May; processes across 15 Oct → 14 Dec, with
3131
per-task shifts from `deadlines.yml`.
32-
- CSS is bundled locally (no CDN, no JS); HTML pages remain self-contained for offline use.
32+
- CSS is bundled locally (no CDN); HTML pages remain self-contained, with JS limited to the variant calculator.

scoreboard/html_renderer.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def render_threads_page(
2424
deadlines: Dict[str, str],
2525
generated_at: str,
2626
inline_css: str,
27+
texts: dict,
2728
repo_salt: str,
2829
threads_vmax: int,
2930
) -> str:
@@ -34,6 +35,7 @@ def render_threads_page(
3435
deadlines=deadlines,
3536
generated_at=generated_at,
3637
inline_css=inline_css,
38+
t=texts,
3739
repo_salt=repo_salt,
3840
threads_vmax=threads_vmax,
3941
)
@@ -44,6 +46,7 @@ def render_processes_page(
4446
deadlines: List[str],
4547
generated_at: str,
4648
inline_css: str,
49+
texts: dict,
4750
repo_salt: str,
4851
proc_vmaxes: list[int],
4952
) -> str:
@@ -54,6 +57,7 @@ def render_processes_page(
5457
deadlines=deadlines,
5558
generated_at=generated_at,
5659
inline_css=inline_css,
60+
t=texts,
5761
repo_salt=repo_salt,
5862
proc_vmaxes=proc_vmaxes,
5963
)
@@ -65,6 +69,7 @@ def render_index_page(
6569
has_variants_page: bool,
6670
generated_at: str,
6771
inline_css: str,
72+
texts: dict,
6873
) -> str:
6974
env = _environment()
7075
template = env.get_template("index_menu.html.j2")
@@ -74,6 +79,7 @@ def render_index_page(
7479
has_variants_page=has_variants_page,
7580
generated_at=generated_at,
7681
inline_css=inline_css,
82+
t=texts,
7783
)
7884

7985

@@ -84,6 +90,7 @@ def render_variants_page(
8490
repo_salt: str,
8591
threads_vmax: int,
8692
proc_vmaxes: list[int],
93+
texts: dict,
8794
) -> str:
8895
env = _environment()
8996
template = env.get_template("variants.html.j2")
@@ -94,6 +101,7 @@ def render_variants_page(
94101
repo_salt=repo_salt,
95102
threads_vmax=threads_vmax,
96103
proc_vmaxes=proc_vmaxes,
104+
t=texts,
97105
)
98106

99107

scoreboard/main.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
render_variants_page,
3636
)
3737
from metrics import compute_variant_threads, compute_variants_processes
38+
from texts import TEXT
3839

3940
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
4041
logger = logging.getLogger(__name__)
@@ -224,6 +225,7 @@ def _proc_vmax(task_number: int) -> int:
224225
threads_deadlines_labels,
225226
generated_at,
226227
css,
228+
texts=TEXT,
227229
repo_salt=REPO_SALT,
228230
threads_vmax=threads_vmax,
229231
)
@@ -232,6 +234,7 @@ def _proc_vmax(task_number: int) -> int:
232234
processes_deadlines_labels,
233235
generated_at,
234236
css,
237+
texts=TEXT,
235238
repo_salt=REPO_SALT,
236239
proc_vmaxes=proc_vmaxes,
237240
)
@@ -261,6 +264,7 @@ def _proc_vmax(task_number: int) -> int:
261264
threads_deadlines_labels,
262265
generated_at,
263266
css,
267+
texts=TEXT,
264268
repo_salt=REPO_SALT,
265269
threads_vmax=threads_vmax,
266270
)
@@ -289,6 +293,7 @@ def _proc_vmax(task_number: int) -> int:
289293
processes_deadlines_labels,
290294
generated_at,
291295
css,
296+
texts=TEXT,
292297
repo_salt=REPO_SALT,
293298
proc_vmaxes=proc_vmaxes,
294299
)
@@ -326,6 +331,7 @@ def _proc_vmax(task_number: int) -> int:
326331
repo_salt=REPO_SALT,
327332
threads_vmax=threads_vmax,
328333
proc_vmaxes=proc_vmaxes,
334+
texts=TEXT,
329335
)
330336
(output_dir / "variants.html").write_text(variants_html, encoding="utf-8")
331337

@@ -343,6 +349,7 @@ def _proc_vmax(task_number: int) -> int:
343349
has_variants_page=True,
344350
generated_at=generated_at,
345351
inline_css=css,
352+
texts=TEXT,
346353
)
347354
(output_dir / "index.html").write_text(index_html, encoding="utf-8")
348355

scoreboard/static/main.css

Lines changed: 186 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,221 @@
11
:root {
2-
--bg: #f9fafb;
2+
--bg: radial-gradient(120% 120% at 20% 20%, #f8fbff 0%, #eef2ff 45%, #f5f7fb 100%);
33
--card: #ffffff;
44
--text: #0f172a;
5-
--muted: #64748b;
6-
--accent: #2563eb;
7-
--border: #e2e8f0;
8-
--success: #22c55e1f;
9-
--warn: #f973161f;
10-
--muted-cell: #cbd5e11f;
11-
--perf: #2563eb14;
5+
--muted: #52616b;
6+
--accent: #0b57d0;
7+
--accent-weak: #e7f0ff;
8+
--border: #e3e7ef;
9+
--success: #22c55e1a;
10+
--warn: #f973161a;
11+
--muted-cell: #cbd5e11a;
12+
--perf: #0b57d018;
13+
--shadow-lg: 0 16px 40px rgba(15, 23, 42, 0.14);
14+
--shadow-sm: 0 10px 26px rgba(15, 23, 42, 0.10);
1215
}
1316

1417
* { box-sizing: border-box; }
1518
body {
1619
margin: 0;
17-
padding: 24px;
20+
min-height: 100vh;
21+
padding: 28px 24px 44px;
1822
background: var(--bg);
1923
color: var(--text);
20-
font: 15px/1.5 "Roboto", "Inter", "Segoe UI", sans-serif;
24+
font: 15px/1.65 "Roboto", "Segoe UI", "Inter", system-ui, -apple-system, sans-serif;
25+
box-sizing: border-box;
2126
}
2227

23-
h1, h2, h3 { margin: 0 0 8px 0; font-weight: 700; }
28+
.page-shell {
29+
width: min(1180px, 96vw);
30+
margin: 0 auto;
31+
padding: clamp(12px, 3vw, 24px);
32+
box-sizing: border-box;
33+
}
34+
35+
.page-shell--full {
36+
width: 100%;
37+
max-width: none;
38+
margin: 0;
39+
padding: 0 clamp(12px, 3vw, 20px);
40+
}
41+
42+
h1, h2, h3 { margin: 0 0 12px 0; letter-spacing: -0.01em; }
43+
h1 { font-weight: 900; font-size: 28px; }
44+
h2 { font-weight: 800; font-size: 20px; }
45+
h3 { font-weight: 700; font-size: 17px; color: var(--muted); }
2446

2547
.page-header {
2648
display: flex;
27-
justify-content: space-between;
28-
align-items: flex-end;
29-
padding: 14px 16px;
30-
margin-bottom: 16px;
31-
background: var(--card);
49+
flex-direction: column;
50+
align-items: flex-start;
51+
gap: 10px;
52+
padding: 18px 20px;
53+
margin-bottom: 20px;
54+
background: linear-gradient(135deg, #fff, #f3f7ff);
3255
border: 1px solid var(--border);
33-
border-radius: 16px;
34-
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
56+
border-radius: 18px;
57+
box-shadow: var(--shadow-lg);
3558
}
3659

3760
.meta { color: var(--muted); margin: 0; }
3861

39-
.legend { display: flex; gap: 10px; flex-wrap: wrap; color: var(--muted); }
40-
.legend span { background: #f1f5f9; padding: 4px 8px; border-radius: 8px; border: 1px solid var(--border); }
62+
.legend {
63+
display: flex;
64+
gap: 10px;
65+
flex-wrap: wrap;
66+
color: var(--muted);
67+
}
68+
.legend span {
69+
padding: 4px 2px;
70+
border-radius: 6px;
71+
border: none;
72+
background: transparent;
73+
font-weight: 600;
74+
color: var(--muted);
75+
}
4176

42-
.nav-section { margin: 16px 0; background: var(--card); padding: 12px 14px; border: 1px solid var(--border); border-radius: 14px; box-shadow: 0 4px 16px rgba(15, 23, 42, 0.05); }
43-
.link-list { list-style: none; padding-left: 0; margin: 4px 0; }
44-
.link-list li { margin: 4px 0; }
45-
.link-list a { color: var(--text); text-decoration: none; border-bottom: 1px solid transparent; }
46-
.link-list a:hover { color: var(--accent); border-color: var(--accent); }
47-
.group-columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
77+
.nav-section {
78+
margin: 18px 0;
79+
background: var(--card);
80+
padding: 16px 18px;
81+
border: 1px solid var(--border);
82+
border-radius: 18px;
83+
box-shadow: var(--shadow-sm);
84+
}
85+
.link-list {
86+
list-style: none;
87+
padding-left: 0;
88+
margin: 6px 0;
89+
display: flex;
90+
flex-direction: column;
91+
gap: 8px;
92+
}
93+
.link-list li { margin: 0; }
94+
.link-list a {
95+
display: inline-flex;
96+
align-items: center;
97+
gap: 6px;
98+
width: 100%;
99+
justify-content: center;
100+
color: var(--text);
101+
text-decoration: none;
102+
font-weight: 700;
103+
letter-spacing: 0.01em;
104+
background: var(--accent-weak);
105+
padding: 8px 12px;
106+
border-radius: 12px;
107+
border: 1px solid var(--border);
108+
box-shadow: 0 4px 10px rgba(15, 23, 42, 0.06);
109+
transition: transform 0.1s ease, box-shadow 0.12s ease, border-color 0.12s ease;
110+
}
111+
.link-list a:hover {
112+
color: var(--accent);
113+
border-color: var(--accent);
114+
box-shadow: 0 8px 16px rgba(11, 87, 208, 0.16);
115+
transform: translateY(-1px);
116+
}
117+
.group-columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; }
48118

49-
.score-table { width: 100%; border-collapse: collapse; margin-top: 12px; background: var(--card); border: 1px solid var(--border); box-shadow: 0 6px 18px rgba(15, 23, 42, 0.04); }
50-
.score-table th, .score-table td { border: 1px solid var(--border); padding: 8px 10px; text-align: center; }
51-
.score-table th { background: #f8fafc; color: var(--text); font-weight: 700; }
52-
.score-table .task-header { background: #eef2ff; }
119+
.score-table {
120+
width: 100%;
121+
border-collapse: collapse;
122+
margin-top: 12px;
123+
background: var(--card);
124+
border: 1px solid var(--border);
125+
box-shadow: var(--shadow-sm);
126+
border-radius: 16px;
127+
overflow: hidden;
128+
}
129+
.score-table th, .score-table td {
130+
border: 1px solid var(--border);
131+
padding: 10px 11px;
132+
text-align: center;
133+
}
134+
.score-table tbody tr {
135+
transition: background-color 0.12s ease, box-shadow 0.12s ease;
136+
}
137+
.score-table th {
138+
background: #f6f8fc;
139+
color: var(--text);
140+
font-weight: 800;
141+
}
142+
.score-table .task-header { background: #eaf0ff; }
143+
.score-table tbody tr:hover {
144+
background: #eaf0ff;
145+
box-shadow: inset 3px 0 0 var(--accent);
146+
}
53147
.task-title { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
54-
.task-name { text-transform: uppercase; letter-spacing: 0.08em; font-weight: 700; color: #1e293b; }
55-
.deadline { color: #dc2626; font-weight: 700; }
148+
.task-name { text-transform: uppercase; letter-spacing: 0.08em; font-weight: 800; color: #1e293b; }
149+
.deadline { color: #d32f2f; font-weight: 800; }
56150

57-
.name { text-align: left; font-weight: 700; }
151+
.name { text-align: left; font-weight: 800; }
58152
.narrow { white-space: nowrap; }
59-
.total { font-weight: 800; color: #111827; }
153+
.total { font-weight: 900; color: #0f172a; }
60154

61155
.cell-success { background: var(--success); }
62156
.cell-warning { background: var(--warn); }
63157
.cell-muted { background: var(--muted-cell); }
64158
.cell-empty { background: #f8fafc; color: var(--muted); }
65159
.perf-cell { background: var(--perf); }
66-
.deadline-cell { color: #dc2626; }
160+
.deadline-cell { color: #d32f2f; }
67161
.muted { color: var(--muted); }
68162

163+
button {
164+
background: var(--accent);
165+
color: #fff;
166+
border: none;
167+
border-radius: 12px;
168+
padding: 11px 15px;
169+
font-weight: 800;
170+
cursor: pointer;
171+
box-shadow: 0 6px 14px rgba(11, 87, 208, 0.28);
172+
transition: transform 0.1s ease, box-shadow 0.12s ease, filter 0.12s ease;
173+
}
174+
button:hover { transform: translateY(-1px); box-shadow: 0 10px 22px rgba(11, 87, 208, 0.32); filter: brightness(1.02); }
175+
button:active { transform: translateY(0); box-shadow: 0 5px 12px rgba(11, 87, 208, 0.24); }
176+
177+
input[type="text"] {
178+
border: 1px solid var(--border);
179+
border-radius: 10px;
180+
padding: 9px 11px;
181+
min-width: 150px;
182+
font-size: 14px;
183+
background: #f9fbff;
184+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
185+
}
186+
input[type="text"]:focus {
187+
outline: none;
188+
border-color: var(--accent);
189+
box-shadow: 0 0 0 3px rgba(11, 87, 208, 0.15);
190+
}
191+
192+
.action-link {
193+
display: inline-flex;
194+
align-items: center;
195+
gap: 6px;
196+
padding: 8px 12px;
197+
background: var(--accent-weak);
198+
border: 1px solid var(--border);
199+
border-radius: 12px;
200+
font-weight: 700;
201+
color: var(--text);
202+
text-decoration: none;
203+
box-shadow: 0 4px 10px rgba(15, 23, 42, 0.06);
204+
transition: transform 0.1s ease, box-shadow 0.12s ease, border-color 0.12s ease;
205+
}
206+
.action-link:hover {
207+
color: var(--accent);
208+
border-color: var(--accent);
209+
box-shadow: 0 8px 16px rgba(11, 87, 208, 0.16);
210+
transform: translateY(-1px);
211+
}
212+
69213
@media (max-width: 1100px) {
70-
body { padding: 12px; font-size: 14px; }
71-
.score-table th, .score-table td { padding: 6px 6px; }
214+
body { padding: 18px; font-size: 14px; }
215+
.score-table th, .score-table td { padding: 8px 8px; }
72216
}
217+
218+
.legend-table { border-collapse: collapse; margin-top: 6px; }
219+
.legend-table td { padding: 5px 10px; border: none; text-align: left; }
220+
.legend-term { font-weight: 900; color: var(--text); white-space: nowrap; }
221+
.legend-def { color: var(--muted); }

0 commit comments

Comments
 (0)