Redesign B3 bordeaux + finish lab-page rework

- Lab page is now read-only with a settings screen for levels/variants;
  variant issuance moved to a large modal on the group page (assign-fragment).
- Header: bordeaux logo and 2px accent underline, bordeaux login/logout/theme
  buttons; smaller radii across the UI; filled secondary/danger buttons.
- Includes prior agent work (login backoff, no-cache pages, API timeouts,
  content tweaks) and fixes (variant usage Map.values, import order).
- Bump static asset version to v=9.
This commit is contained in:
2026-10-02 15:41:16 +03:00
parent c0cf11a5a3
commit 51d1b84297
67 changed files with 3019 additions and 653 deletions

View File

@@ -4,16 +4,26 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title th:replace="${title}">LMS</title>
<link rel="icon" type="image/svg+xml" th:href="@{/favicon.svg}">
<link rel="stylesheet" th:href="@{/css/app.css}">
<link id="hljs-light" rel="stylesheet" th:href="@{/css/hljs-github.css}">
<link id="hljs-dark" rel="stylesheet" th:href="@{/css/hljs-github-dark.css}" disabled>
<script th:src="@{/js/htmx.min.js}" defer></script>
<script th:src="@{/js/highlight.min.js}" defer></script>
<script th:src="@{/js/table-scroll.js}" defer></script>
<script th:src="@{/js/tablekit.js}" defer></script>
<script th:src="@{/js/ui-state.js}" defer></script>
<script th:src="@{/js/md-content.js}" defer></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Inter с запасным системным гротеском. Загрузка не блокирует рендер:
при недоступном CDN страница открывается на системном шрифте. -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
rel="stylesheet" media="print" onload="this.media='all'">
<noscript>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
</noscript>
<link rel="icon" type="image/svg+xml" th:href="@{/favicon.svg(v='2')}">
<!-- v=4: сброс кэша стилей после редизайна; при изменении css поднимать версию -->
<link rel="stylesheet" th:href="@{/css/app.css(v='9')}">
<link id="hljs-light" rel="stylesheet" th:href="@{/css/hljs-github.css(v='9')}">
<link id="hljs-dark" rel="stylesheet" th:href="@{/css/hljs-github-dark.css(v='9')}" disabled>
<script th:src="@{/js/highlight.min.js(v='9')}" defer></script>
<script th:src="@{/js/table-scroll.js(v='9')}" defer></script>
<script th:src="@{/js/tablekit.js(v='9')}" defer></script>
<script th:src="@{/js/ui-state.js(v='9')}" defer></script>
<script th:src="@{/js/md-content.js(v='9')}" defer></script>
<script th:src="@{/js/submit-guard.js(v='9')}" defer></script>
<script>
(function(){
var t = localStorage.getItem('lms-theme') || 'light';
@@ -99,8 +109,8 @@
<a th:if="${currentUser.admin}" th:href="@{/admin}" class="nav-link nav-user" th:text="${currentUser.name}" title="Личный кабинет">—</a>
<a th:unless="${currentUser.admin}" th:href="@{/student}" class="nav-link nav-user" th:text="${currentUser.name}" title="Личный кабинет">—</a>
</th:block>
<a th:if="${currentUser == null}" th:href="@{/login}" class="btn btn-secondary btn-sm">Войти</a>
<button type="button" onclick="toggleTheme()" class="btn btn-secondary btn-sm theme-btn"
<a th:if="${currentUser == null}" th:href="@{/login}" class="btn btn-primary">Войти</a>
<button type="button" onclick="toggleTheme()" class="btn btn-primary theme-btn"
aria-label="Сменить тему" title="Сменить тему">
<svg class="theme-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<circle cx="12" cy="12" r="5" fill="none" stroke="currentColor" stroke-width="1.6"/>
@@ -118,7 +128,7 @@
</svg>
</button>
<form th:if="${currentUser != null}" th:action="@{/logout}" method="post">
<button class="btn btn-secondary" type="submit">Выйти</button>
<button class="btn btn-primary" type="submit">Выйти</button>
<input type="hidden" name="_csrf" th:value="${csrfToken}">
</form>
</nav>
@@ -141,7 +151,10 @@
<button class="toast-close" type="button" aria-label="Закрыть" onclick="this.parentElement.remove()">✕</button>
</div>
<div th:if="${resetPassword}" class="toast-item toast-warning" data-toast>
<span class="toast-text">Новый пароль: <code class="code font-mono" th:text="${resetPassword}">password</code></span>
<span class="toast-text">
Новый пароль<span th:if="${resetFor}" th:text="| для ${resetFor}|"> для Иванов И.</span>:
<code class="code font-mono" th:text="${resetPassword}">password</code>
</span>
<button class="toast-close" type="button" aria-label="Закрыть" onclick="this.parentElement.remove()">✕</button>
</div>
</div>