diff --git a/web/src/components/AppUpdateManagementPanel.vue b/web/src/components/AppUpdateManagementPanel.vue
index b716f39..930bffb 100644
--- a/web/src/components/AppUpdateManagementPanel.vue
+++ b/web/src/components/AppUpdateManagementPanel.vue
@@ -422,14 +422,14 @@ onMounted(loadReleases)
}
.update-heading span {
- color: #74818a;
+ color: #5f6f7d;
font-family: "Cascadia Mono", Consolas, monospace;
font-size: 10px;
letter-spacing: .09em;
}
.update-heading h3 { font-size: 17px; margin: 3px 0 4px; }
-.update-heading p { color: #66747e; font-size: 12px; margin: 0; }
+.update-heading p { color: #526273; font-size: 12px; margin: 0; }
.heading-actions { display: flex; gap: 8px; }
.update-rules {
@@ -443,7 +443,7 @@ onMounted(loadReleases)
}
.update-rules strong { color: #1f7468; font-size: 11px; }
-.update-rules span { color: #66747e; font-size: 11px; }
+.update-rules span { color: #526273; font-size: 11px; }
.update-rules span::before { content: "ยท"; margin-right: 8px; }
.update-filter {
@@ -457,11 +457,11 @@ onMounted(loadReleases)
.file-name { display: block; font-family: "Cascadia Mono", Consolas, monospace; }
.version { color: #1f7468; font-size: 13px; }
.file-name { font-size: 11px; }
-small { color: #74818a; display: block; font-size: 10px; margin-top: 4px; }
+small { color: #5f6f7d; display: block; font-size: 10px; margin-top: 4px; }
.active-label { color: #1f7468; font-size: 11px; font-weight: 700; }
.el-table { margin-top: 12px; }
.cursor-pager { align-items:center; display:flex; gap:10px; justify-content:flex-end; padding:16px 18px; }
-.cursor-pager span { color:#74818a; font-size:11px; }
+.cursor-pager span { color:#5f6f7d; font-size:11px; }
.form-grid { display: grid; gap: 0 14px; grid-template-columns: 1fr 1fr; }
.form-grid .el-select { width: 100%; }
diff --git a/web/src/components/CourseDiscussionPanel.vue b/web/src/components/CourseDiscussionPanel.vue
index 1908859..114c747 100644
--- a/web/src/components/CourseDiscussionPanel.vue
+++ b/web/src/components/CourseDiscussionPanel.vue
@@ -70,5 +70,5 @@ watch(() => props.sheetId, () => load(true), { immediate: true })
diff --git a/web/src/style.css b/web/src/style.css
index 6705bde..ffd4c5d 100644
--- a/web/src/style.css
+++ b/web/src/style.css
@@ -5,7 +5,7 @@
font-synthesis: none;
text-rendering: optimizeLegibility;
--ink: #182033;
- --muted: #6d7689;
+ --muted: #59677a;
--indigo: #233876;
--indigo-deep: #152550;
--teal: #087f73;
@@ -1530,6 +1530,9 @@ button { cursor: pointer; }
--el-border-color: #dfe5ee;
--el-text-color-primary: #182033;
--el-text-color-regular: #4f5b6c;
+ --el-text-color-secondary: #5b687a;
+ --el-text-color-placeholder: #637184;
+ --el-text-color-disabled: #566577;
}
body {
diff --git a/web/src/views/AcademicCalendarView.vue b/web/src/views/AcademicCalendarView.vue
index 6c7f2c2..da31996 100644
--- a/web/src/views/AcademicCalendarView.vue
+++ b/web/src/views/AcademicCalendarView.vue
@@ -777,10 +777,10 @@ onMounted(async () => {
}
.range-separator {
padding: 0 8px;
- color: #7b8794;
+ color: #627184;
}
.form-hint {
- color: #718096;
+ color: #5f6f83;
font-size: 13px;
}
.holiday-sync-intro {
diff --git a/web/src/views/AccountActivationView.vue b/web/src/views/AccountActivationView.vue
index d0291a1..576235e 100644
--- a/web/src/views/AccountActivationView.vue
+++ b/web/src/views/AccountActivationView.vue
@@ -256,9 +256,9 @@ onMounted(async () => {
.activation-form { margin-top: 24px; }
.activation-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.activation-form :deep(.el-select) { width: 100%; }
-.password-rule { margin: -4px 0 18px; color: #738596; font-size: 12px; }
+.password-rule { margin: -4px 0 18px; color: #5d7185; font-size: 12px; }
.activation-submit { width: 100%; }
-footer { margin-top: 22px; color: #718191; font-size: 13px; text-align: center; }
+footer { margin-top: 22px; color: #5e7084; font-size: 13px; text-align: center; }
footer a { color: #176b87; font-weight: 700; text-decoration: none; }
@media (max-width: 640px) {
.activation-page { padding: 0 14px 28px; }
diff --git a/web/src/views/AccountView.vue b/web/src/views/AccountView.vue
index 62f8d33..148cc4f 100644
--- a/web/src/views/AccountView.vue
+++ b/web/src/views/AccountView.vue
@@ -190,7 +190,7 @@ onMounted(async () => {
.card-title h2 { margin: 6px 0 0; color: #263445; font-size: 21px; }
dl { margin: 6px 0 0; }
dl > div { display: grid; grid-template-columns: 100px 1fr; gap: 18px; padding: 16px 0; border-bottom: 1px solid #f0f2f4; }
-dt { color: #7b8794; font-size: 13px; }
+dt { color: #627184; font-size: 13px; }
dd { margin: 0; color: #263445; font-weight: 650; }
.role-list { display: flex; flex-wrap: wrap; gap: 7px; }
.sso-description { min-height: 54px; margin: 22px 0; color: #65717e; line-height: 1.7; }
diff --git a/web/src/views/EvaluationView.vue b/web/src/views/EvaluationView.vue
index 098d8a1..64c0695 100644
--- a/web/src/views/EvaluationView.vue
+++ b/web/src/views/EvaluationView.vue
@@ -396,15 +396,15 @@ onMounted(async () => {
{{ evalScores[dim.id] ?? 0 }} / {{ dim.maxScore }} ๅ
- 0
+ 0
- {{ dim.maxScore }}
+ {{ dim.maxScore }}
{{ evalTotal }}
- / 100 ๅ
+ / 100 ๅ
@@ -485,12 +485,12 @@ onMounted(async () => {
.eval-task-info { display: flex; flex-direction: column; gap: 2px }
.eval-task-info b { font-size: 15px }
.eval-task-info span { font-size: 13px; color: #606266 }
-.eval-task-info small { font-size: 12px; color: #909399 }
+.eval-task-info small { font-size: 12px; color: #5f6b7a }
.eval-header { text-align: center }
.eval-header b { display: block; font-size: 16px }
.eval-header span { color: #606266; font-size: 14px }
-.eval-header small { display: block; color: #909399; font-size: 12px }
+.eval-header small { display: block; color: #5f6b7a; font-size: 12px }
.teacher-result-card {
border: 1px solid #e4e7ed; border-radius: 8px; padding: 16px; margin-bottom: 12px;
@@ -498,16 +498,16 @@ onMounted(async () => {
.result-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px }
.result-header b { display: block; font-size: 16px }
.result-header span { font-size: 13px; color: #606266 }
-.result-header small { display: block; font-size: 12px; color: #909399 }
+.result-header small { display: block; font-size: 12px; color: #5f6b7a }
.result-total { text-align: right }
.result-total span { font-size: 28px; font-weight: 700 }
-.result-total small { display: block; font-size: 12px; color: #909399 }
+.result-total small { display: block; font-size: 12px; color: #5f6b7a }
.result-dims { display: flex; flex-direction: column; gap: 8px }
.result-dim-item { display: flex; align-items: center }
.result-dim-item > span { width: 80px; font-size: 13px; flex-shrink: 0 }
.result-dim-item b { font-size: 14px; min-width: 60px; text-align: right }
-.result-dim-item b small { font-size: 11px; color: #909399; font-weight: 400 }
+.result-dim-item b small { font-size: 11px; color: #5f6b7a; font-weight: 400 }
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px }
.closure-card { margin-top: 18px }
diff --git a/web/src/views/FreeClassroomsView.vue b/web/src/views/FreeClassroomsView.vue
index e8c35bb..e064c12 100644
--- a/web/src/views/FreeClassroomsView.vue
+++ b/web/src/views/FreeClassroomsView.vue
@@ -375,7 +375,7 @@ h2 {
.page-heading p {
margin-top: 8px;
- color: #718096;
+ color: #5f6f83;
}
.query-card {
@@ -432,7 +432,7 @@ h2 {
.result-heading span,
.building-groups small,
.room-item span {
- color: #718096;
+ color: #5f6f83;
font-size: 13px;
}
diff --git a/web/src/views/GradeAnalyticsView.vue b/web/src/views/GradeAnalyticsView.vue
index f999a4b..4333444 100644
--- a/web/src/views/GradeAnalyticsView.vue
+++ b/web/src/views/GradeAnalyticsView.vue
@@ -908,7 +908,7 @@ onBeforeUnmount(() => {
.class-option > * { display: block; }
.class-option strong { margin: 5px 0; color: var(--ink); font-size: 14px; }
.class-option small { overflow: hidden; color: #667085; text-overflow: ellipsis; white-space: nowrap; }
-.course-code { color: #7b8797; font: 11px Consolas, monospace; }
+.course-code { color: #5e6e82; font: 11px Consolas, monospace; }
.option-metrics { margin-top: 9px; color: #667085; font-size: 11px; }
.option-metrics b { color: var(--blue); font: 700 13px Consolas, monospace; }
.class-rail :deep(.el-pagination) { justify-content: center; padding: 14px 4px; }
@@ -937,7 +937,7 @@ onBeforeUnmount(() => {
.chart-card { min-width: 0; padding: 20px 22px; background: #fff; border: 1px solid var(--line); box-shadow: 0 10px 28px rgb(24 50 79 / 5%); }
.chart-card-wide { grid-column: 1 / -1; }
.chart-card header { display: flex; min-width: 0; align-items: end; justify-content: space-between; gap: 18px; }
-.chart-card header span { color: #7b8797; font: 10px Consolas, monospace; letter-spacing: .08em; }
+.chart-card header span { color: #5e6e82; font: 10px Consolas, monospace; letter-spacing: .08em; }
.chart-card h4 { margin: 5px 0 0; color: var(--ink); font-size: 16px; }
.chart-card header p, .chart-caption { min-width: 0; margin: 0; color: #667085; font-size: 11px; text-align: right; }
.chart-caption { margin-top: 9px; text-align: left; }
@@ -952,7 +952,7 @@ onBeforeUnmount(() => {
.date-metric { font-size: 17px !important; }
.analytics-comments { margin-top: 26px; padding: 22px; background: #fff; border: 1px solid var(--line); box-shadow: 0 10px 28px rgb(24 50 79 / 5%); }
.analytics-comments > header { display: flex; justify-content: space-between; gap: 18px; margin-bottom: 16px; }
-.analytics-comments header span { color: #7b8797; font: 10px Consolas, monospace; letter-spacing: .08em; }
+.analytics-comments header span { color: #5e6e82; font: 10px Consolas, monospace; letter-spacing: .08em; }
.analytics-comments h3 { margin: 5px 0; color: var(--ink); font-size: 17px; }
.analytics-comments header p { margin: 0; color: #667085; font-size: 12px; }
.comment-compose-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 10px; }
diff --git a/web/src/views/NotificationCenterView.vue b/web/src/views/NotificationCenterView.vue
index 72d731a..023e826 100644
--- a/web/src/views/NotificationCenterView.vue
+++ b/web/src/views/NotificationCenterView.vue
@@ -1101,7 +1101,7 @@ onMounted(() => load())
.message-main time,
.sent-card time {
- color: #94a3b8;
+ color: #62748a;
font-size: 12px;
}
@@ -1149,7 +1149,7 @@ onMounted(() => load())
gap: 8px 18px;
margin-top: auto;
padding-top: 12px;
- color: #94a3b8;
+ color: #62748a;
font-size: 12px;
}
@@ -1500,7 +1500,7 @@ onMounted(() => load())
display: block;
width: 100%;
margin-top: 6px;
- color: #94a3b8;
+ color: #62748a;
line-height: 1.5;
}
@@ -1511,7 +1511,7 @@ onMounted(() => load())
}
.task-option small {
- color: #94a3b8;
+ color: #62748a;
}
.editor-form-item :deep(.el-form-item__content) {
diff --git a/web/src/views/TimetableView.vue b/web/src/views/TimetableView.vue
index 7b33dd1..8485b85 100644
--- a/web/src/views/TimetableView.vue
+++ b/web/src/views/TimetableView.vue
@@ -1362,7 +1362,7 @@ onMounted(async () => {
.resource-type-tabs { flex-shrink: 0; }
.hierarchy-filters { min-width: 0; display: grid; grid-template-columns: repeat(4, minmax(140px, 1fr)); gap: 10px; }
.hierarchy-filters.compact { grid-template-columns: repeat(3, minmax(170px, 260px)); }
-.resource-filter-panel > small { color: #718191; white-space: nowrap; }
+.resource-filter-panel > small { color: #5e7084; white-space: nowrap; }
.timetable-sheet { min-width: 0; min-height: 360px; padding: 22px; background: #fff; border: 1px solid #dce4eb; }
.timetable-toolbar { margin-bottom: 16px; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.timetable-toolbar > div { display: flex; align-items: center; gap: 10px; }
@@ -1389,12 +1389,12 @@ onMounted(async () => {
.timetable-export-area { min-width: 0; padding: 2px; background: #fff; }
.batch-export-panel { display: grid; gap: 10px; width: min(760px, 100%); margin-top: 8px; padding: 14px; border: 1px solid #d7e3ea; border-radius: 8px; background: #f8fbfc; }
.batch-export-heading { display: flex; align-items: baseline; gap: 10px; color: #17324d; }
-.batch-export-heading span, .batch-export-actions span { color: #718191; font-size: 12px; }
+.batch-export-heading span, .batch-export-actions span { color: #5e7084; font-size: 12px; }
.batch-export-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.sheet-meta { display: flex; justify-content: space-between; gap: 24px; margin-bottom: 18px; padding-bottom: 16px; border-bottom: 1px solid #e6ebf0; }
.sheet-meta div { display: grid; gap: 3px; }
.sheet-meta strong { color: #17324d; }
-.sheet-meta span { color: #718191; font-size: 13px; }
+.sheet-meta span { color: #5e7084; font-size: 13px; }
.flexible-courses { margin-bottom: 20px; border: 1px solid #cfe1dc; background: #f5faf8; }
.flexible-heading { padding: 12px 15px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #dce9e5; }
.flexible-heading > div { display: flex; align-items: baseline; gap: 10px; }
@@ -1412,7 +1412,7 @@ onMounted(async () => {
.view-context { min-height: 54px; padding: 10px 14px; display: flex; align-items: center; justify-content: space-between; gap: 20px; border-bottom: 1px solid #dce4eb; background: #f8fafb; }
.view-context > div { display: grid; gap: 3px; }
.view-context strong { color: #17324d; font-size: 14px; }
-.view-context span, .view-context small { color: #718191; font-size: 11px; }
+.view-context span, .view-context small { color: #5e7084; font-size: 11px; }
.week-grid { min-width: 1120px; display: grid; grid-template-columns: 92px repeat(7, minmax(140px, 1fr)); position: relative; }
.grid-corner, .day-head, .period-head { z-index: 2; background: #f3f6f8; border: 1px solid #dce4eb; color: #3f5366; }
.grid-corner, .day-head { display: grid; place-items: center; font-weight: 700; }
diff --git a/web/vite.config.ts b/web/vite.config.ts
index d8e29da..8d466f3 100644
--- a/web/vite.config.ts
+++ b/web/vite.config.ts
@@ -60,6 +60,10 @@ export default defineConfig(({ mode }) => {
},
workbox: {
cleanupOutdatedCaches: true,
+ // `skipWaiting()` only activates the new worker. Claiming existing
+ // clients is what fires `controllerchange` in pwa.ts and reloads the
+ // open page immediately after the user confirms the update.
+ clientsClaim: true,
navigateFallback: '/index.html',
navigateFallbackDenylist: [/^\/api\//],
runtimeCaching: [