Files
LMS/design/style-b2-minimal.html
Vladislav Kopnin 51d1b84297 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.
2026-10-02 15:41:16 +03:00

401 lines
24 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru" data-accent="blue">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LMS — демо стиля Б2: минимализм с живой палитрой</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
/* ─────────────────────────────────────────────────────────
База: нейтральный каркас + насыщенные ФУНКЦИОНАЛЬНЫЕ цвета.
Акцент выбирается переключателем сверху (data-accent на <html>).
───────────────────────────────────────────────────────── */
:root {
--bg: #f6f7f9;
--surface: #ffffff;
--surface-2: #f9fafb;
--text: #14161a;
--muted: #6b7280;
--border: #e5e7eb;
--border-strong: #d2d5db;
--ok: #15803d;
--ok-soft: #e8f6ee;
--warn: #b45309;
--warn-soft: #fdf3e3;
--danger: #c62828;
--danger-soft: #fdecec;
--info: #0369a1;
--info-soft: #e6f3fa;
--radius: 10px;
--radius-sm: 8px;
}
/* Акценты */
:root[data-accent="blue"] { --accent: #2563eb; --accent-hover: #1d4fd7; --accent-soft: #eaf1ff; }
:root[data-accent="emerald"] { --accent: #0e7a55; --accent-hover: #0a5e42; --accent-soft: #e6f5ef; }
:root[data-accent="violet"] { --accent: #6d28d9; --accent-hover: #5b21b6; --accent-soft: #f1ebfe; }
:root[data-accent="graphite"]{ --accent: #23252b; --accent-hover: #000000; --accent-soft: #eef0f3; }
* { box-sizing: border-box; }
body {
margin: 0; background: var(--bg); color: var(--text);
font: 14px/1.55 "Inter", "Segoe UI", system-ui, sans-serif;
-webkit-font-smoothing: antialiased;
}
.demo-note {
background: #14161a; color: #fafafa; text-align: center;
font-size: 12px; padding: 6px 12px;
}
/* ── Переключатель акцента ─────────────────────────────── */
.accent-bar {
background: var(--surface); border-bottom: 1px solid var(--border);
display: flex; align-items: center; justify-content: center; gap: 10px;
padding: 8px 12px; font-size: 12.5px; color: var(--muted); flex-wrap: wrap;
}
.swatch {
width: 20px; height: 20px; border-radius: 50%; border: 2px solid transparent;
cursor: pointer; padding: 0; display: inline-flex; align-items: center; justify-content: center;
}
.swatch span { width: 12px; height: 12px; border-radius: 50%; display: block; }
.swatch.active { border-color: var(--text); }
.sw-blue span { background: #2563eb; }
.sw-emerald span { background: #0e7a55; }
.sw-violet span { background: #6d28d9; }
.sw-graphite span{ background: #23252b; }
/* ── Шапка ─────────────────────────────────────────────── */
.page-header { background: var(--surface); border-bottom: 1px solid var(--border); }
.header-inner {
max-width: 1200px; margin: 0 auto; padding: 14px 28px;
display: flex; align-items: center; gap: 28px; flex-wrap: wrap;
}
.logo { font-size: 16px; font-weight: 700; color: var(--text); text-decoration: none; }
.logo b { color: var(--accent); }
.nav { display: flex; gap: 4px; flex: 1; flex-wrap: wrap; }
.nav a {
color: var(--muted); text-decoration: none; font-size: 13.5px; font-weight: 500;
padding: 6px 12px; border-radius: var(--radius-sm);
}
.nav a.active { color: var(--accent); background: var(--accent-soft); }
.nav a:hover { color: var(--text); background: var(--surface-2); }
.user { display: flex; align-items: center; gap: 12px; font-size: 13.5px; }
/* ── Основная часть ────────────────────────────────────── */
.page-main { max-width: 1200px; margin: 0 auto; padding: 36px 28px 56px; }
.kicker { margin: 0 0 6px; font-size: 12px; font-weight: 500; color: var(--muted); }
h1 { margin: 0 0 8px; font-size: 24px; font-weight: 700; letter-spacing: -.02em; }
h1 small { color: var(--accent); font-weight: 700; }
.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 24px; flex-wrap: wrap; }
.meta { margin: 0; color: var(--muted); font-size: 13px; }
.meta b { color: var(--ok); }
.actions { display: flex; gap: 8px; flex-wrap: wrap; }
.toolbar { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
/* ── Таблица-ведомость ─────────────────────────────────── */
.matrix-wrap { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
table.matrix { width: 100%; border-collapse: collapse; }
.matrix th {
padding: 12px 16px; text-align: left; background: var(--surface-2);
font-size: 12px; font-weight: 500; color: var(--muted);
border-bottom: 1px solid var(--border); white-space: nowrap;
}
.matrix td { padding: 14px 16px; border-bottom: 1px solid var(--border); vertical-align: top; }
.matrix tbody tr:last-child td { border-bottom: none; }
.matrix tbody tr:hover { background: #f5f8ff33; }
.sticky { min-width: 200px; }
.student-name { font-weight: 600; font-size: 13.5px; }
.student-no { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; margin-top: 2px; }
.lab-no { display: block; color: var(--text); font-weight: 500; }
.lab-title { display: block; font-weight: 400; font-size: 11.5px; color: var(--muted); }
.cell-main { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.variant { font-weight: 600; font-size: 13.5px; color: var(--accent); }
.score { font-variant-numeric: tabular-nums; font-weight: 700; }
.score.good { color: var(--ok); }
.score.mid { color: var(--warn); }
.score.muted { color: var(--muted); font-weight: 400; }
.cell-sub { margin-top: 6px; display: flex; gap: 14px; font-size: 12px; color: var(--muted); flex-wrap: wrap; }
.sub-link { color: var(--info); text-decoration: none; font-weight: 500; }
.sub-link:hover { text-decoration: underline; }
.st { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.st::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--border-strong); }
.st.done { color: var(--ok); } .st.done::before { background: var(--ok); }
.st.active {
color: var(--warn); background: var(--warn-soft);
padding: 2px 8px; border-radius: 99px; font-weight: 600;
}
.st.active::before { background: var(--warn); }
.st.code { color: var(--muted); }
.eye { cursor: default; color: var(--border-strong); font-size: 12px; }
.eye.on { color: var(--ok); }
.ta-r { text-align: right; }
/* ── Кнопки, бейджи, поля ──────────────────────────────── */
.btn {
display: inline-block; padding: 8px 16px; font: inherit; font-size: 13px; font-weight: 600;
border: 1px solid transparent; background: var(--accent);
color: #fff; border-radius: var(--radius-sm); cursor: pointer; text-decoration: none;
}
.btn:hover { background: var(--accent-hover); }
.btn-secondary { background: var(--surface); border-color: var(--border-strong); color: var(--text); }
.btn-secondary:hover { background: var(--surface-2); }
.btn-danger { background: var(--surface); color: var(--danger); border-color: #efc4c4; }
.btn-danger:hover { background: var(--danger-soft); }
.btn-accent-soft { background: var(--accent-soft); color: var(--accent); }
.btn-accent-soft:hover { background: var(--accent); color: #fff; }
.btn-sm { padding: 5px 10px; font-size: 12px; }
.input {
padding: 8px 12px; font: inherit; font-size: 13px; color: var(--text);
background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
}
.input:focus { outline: 3px solid var(--accent-soft); border-color: var(--accent); }
/* Бейдж — только индикатор статуса, некликабельный, всегда по-русски */
.badge {
display: inline-block; padding: 3px 10px; font-size: 11.5px; font-weight: 600;
border-radius: 6px; background: var(--surface-2); color: var(--muted);
border: 1px solid var(--border); cursor: default;
}
.badge.ok { background: var(--ok-soft); color: var(--ok); border-color: #c4e5d0; }
.badge.warn { background: var(--warn-soft); color: var(--warn); border-color: #ecd3ae; }
.badge.info { background: var(--info-soft); color: var(--info); border-color: #bfdff0; }
.notice {
border: 1px solid var(--border); border-radius: var(--radius);
padding: 12px 16px; font-size: 13px; background: var(--surface);
}
.notice.ok { border-color: #c4e5d0; background: var(--ok-soft); color: #14532d; }
.notice.warn { border-color: #ecd3ae; background: var(--warn-soft); color: #78350f; }
.notice.err { border-color: #efc4c4; background: var(--danger-soft); color: #7f1d1d; }
.notice.info { border-color: #bfdff0; background: var(--info-soft); color: #0c4a6e; }
/* ── Секция элементов ──────────────────────────────────── */
.components { margin-top: 40px; border-top: 1px solid var(--border); padding-top: 24px; }
.row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: 14px 0; }
.status-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.status-line .name { font-weight: 600; min-width: 210px; }
.page-footer { text-align: center; color: var(--muted); font-size: 12px; padding: 24px; }
/* ── Адаптивность таблицы: три режима ─────────────────────
>1080px — полная таблица;
721–1080px — горизонтальный скролл с прилипающей колонкой «Студент»;
≤720px — карточка на каждого студента (шапка таблицы скрыта). */
.table-scroll { border-radius: 12px; }
@media (max-width: 1080px) {
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.matrix { min-width: 860px; }
th.sticky, td.sticky {
position: sticky; left: 0; z-index: 1;
background: var(--surface);
box-shadow: 8px 0 8px -8px rgba(20, 22, 26, .12);
}
thead th.sticky { background: var(--surface-2); }
tbody tr:hover td.sticky { background: #f4f6fb; }
}
@media (max-width: 720px) {
.page-main { padding: 24px 16px 40px; }
.header-inner { padding: 12px 16px; gap: 10px; }
.page-head { display: block; }
.actions { margin-top: 12px; }
.table-scroll { overflow-x: visible; }
.matrix { min-width: 0; }
.matrix thead { display: none; }
.matrix, .matrix tbody, .matrix tr, .matrix td { display: block; width: 100%; }
.matrix tr {
border: 1px solid var(--border); border-radius: 12px;
margin: 0 0 14px; background: var(--surface); overflow: hidden;
}
.matrix td { border-bottom: none; padding: 9px 16px; }
td.sticky {
position: static; box-shadow: none;
background: var(--surface-2);
border-bottom: 1px solid var(--border) !important;
padding: 12px 16px;
}
td[data-label]::before {
content: attr(data-label);
display: block; font-size: 10.5px; font-weight: 600;
letter-spacing: .07em; text-transform: uppercase;
color: var(--muted); margin-bottom: 3px;
}
.ta-r { text-align: left; }
.cell-sub { margin-top: 4px; }
}
</style>
</head>
<body>
<div class="demo-note">Демо-шаблон «Б2» · развитый минимализм: живая палитра, адаптивные таблицы, бейджи-индикаторы · сузьте окно, чтобы увидеть все три режима таблицы</div>
<div class="accent-bar">
Акцентный цвет:
<button class="swatch sw-blue active" title="Синий (рекомендую)" onclick="setAccent('blue', this)"><span></span></button>
<button class="swatch sw-emerald" title="Изумруд" onclick="setAccent('emerald', this)"><span></span></button>
<button class="swatch sw-violet" title="Фиолет" onclick="setAccent('violet', this)"><span></span></button>
<button class="swatch sw-graphite" title="Графит (как в Б1)" onclick="setAccent('graphite', this)"><span></span></button>
<span style="opacity:.6">— красный остаётся только на действиях «Удалить/Отчислить»</span>
</div>
<header class="page-header">
<div class="header-inner">
<a class="logo" href="#">LMS<b>.</b></a>
<nav class="nav">
<a href="#" class="active">Группы</a>
<a href="#">Пользователи</a>
<a href="#">Лабораторные</a>
</nav>
<div class="user">
<span>Копнин В.</span>
<button class="btn btn-secondary btn-sm">Выйти</button>
</div>
</div>
</header>
<main class="page-main">
<div class="page-head">
<div>
<p class="kicker">Ведомость группы</p>
<h1>М202-1 <small>· Первый курс</small></h1>
<p class="meta">24 студента · <b>3 опубликованные</b> лабораторные · длительность защиты 30 мин</p>
</div>
<div class="actions">
<button class="btn btn-secondary">Скачать пароли</button>
<button class="btn">+ Студент</button>
</div>
</div>
<div class="toolbar">
<input class="input" style="width:260px" placeholder="Поиск по студенту…">
<select class="input"><option>все лабораторные</option><option>ЛР 1</option><option>ЛР 2</option></select>
</div>
<div class="matrix-wrap">
<div class="table-scroll">
<table class="matrix">
<thead>
<tr>
<th class="sticky">Студент</th>
<th><span class="lab-no">ЛР 1</span><span class="lab-title">Анализ данных</span></th>
<th><span class="lab-no">ЛР 2</span><span class="lab-title">Сортировки</span></th>
<th><span class="lab-no">ЛР 3</span><span class="lab-title">Деревья поиска</span></th>
<th>Пароль</th>
<th class="ta-r">Действия</th>
</tr>
</thead>
<tbody>
<tr>
<td class="sticky"><div class="student-name">Иванова Анна</div><div class="student-no">2025001</div></td>
<td data-label="ЛР 1 · Анализ данных">
<div class="cell-main"><span class="variant">В-7</span><span class="score good">4.5</span><span class="eye on" title="Оценка опубликована">◉</span></div>
<div class="cell-sub"><a class="sub-link" href="#">сдача ↓ 2</a><span class="st done">защита: готова</span></div>
</td>
<td data-label="ЛР 2 · Сортировки">
<div class="cell-main"><span class="variant">В-2</span><span class="score mid">3.0</span><span class="eye on" title="Оценка опубликована">◉</span></div>
<div class="cell-sub"><a class="sub-link" href="#">сдача ↓ 1</a><span class="st code">защита: код 552314</span></div>
</td>
<td data-label="ЛР 3 · Деревья поиска">
<div class="cell-main"><span class="score muted">—</span></div>
<div class="cell-sub"><span class="st">вариант не выдан</span></div>
</td>
<td data-label="Пароль"><span class="badge warn">временный</span></td>
<td class="ta-r" data-label="Действия"><button class="btn btn-secondary btn-sm">Сбросить</button> <button class="btn btn-sm btn-danger">Отчислить</button></td>
</tr>
<tr>
<td class="sticky"><div class="student-name">Петров Павел</div><div class="student-no">2025002</div></td>
<td data-label="ЛР 1 · Анализ данных">
<div class="cell-main"><span class="variant">В-1</span><span class="score good">5.0</span><span class="eye on" title="Оценка опубликована">◉</span></div>
<div class="cell-sub"><a class="sub-link" href="#">сдача ↓ 3</a><span class="st done">защита: готова</span></div>
</td>
<td data-label="ЛР 2 · Сортировки">
<div class="cell-main"><span class="variant">В-4</span><span class="score muted">—</span><span class="eye on" title="Оценка опубликована">◉</span></div>
<div class="cell-sub"><a class="sub-link" href="#">сдача ↓ 1</a><span class="st active">защита: идёт · 12:34</span></div>
</td>
<td data-label="ЛР 3 · Деревья поиска">
<div class="cell-main"><span class="variant">В-3</span><span class="score muted">—</span><span class="eye" title="Оценка скрыта">○</span></div>
<div class="cell-sub"><span class="st">не сдано</span></div>
</td>
<td data-label="Пароль"><span class="badge ok">изменён</span></td>
<td class="ta-r" data-label="Действия"><button class="btn btn-secondary btn-sm">Сбросить</button> <button class="btn btn-sm btn-danger">Отчислить</button></td>
</tr>
<tr>
<td class="sticky"><div class="student-name">Сидоров Алексей</div><div class="student-no">2025017</div></td>
<td data-label="ЛР 1 · Анализ данных">
<div class="cell-main"><span class="variant">В-5</span><span class="score muted">—</span><span class="eye on" title="Оценка опубликована">◉</span></div>
<div class="cell-sub"><span class="st">не сдано</span></div>
</td>
<td data-label="ЛР 2 · Сортировки">
<div class="cell-main"><span class="score muted">—</span></div>
<div class="cell-sub"><span class="st">вариант не выдан</span></div>
</td>
<td data-label="ЛР 3 · Деревья поиска">
<div class="cell-main"><span class="score muted">—</span></div>
<div class="cell-sub"><span class="st">вариант не выдан</span></div>
</td>
<td data-label="Пароль"><span class="badge warn">временный</span></td>
<td class="ta-r" data-label="Действия"><button class="btn btn-secondary btn-sm">Сбросить</button> <button class="btn btn-sm btn-danger">Отчислить</button></td>
</tr>
<tr>
<td class="sticky"><div class="student-name">Кузнецова Мария</div><div class="student-no">2025021</div></td>
<td data-label="ЛР 1 · Анализ данных">
<div class="cell-main"><span class="variant">В-3</span><span class="score good">4.0</span><span class="eye" title="Оценка скрыта">○</span></div>
<div class="cell-sub"><a class="sub-link" href="#">сдача ↓ 1</a><span class="st done">защита: готова</span></div>
</td>
<td data-label="ЛР 2 · Сортировки">
<div class="cell-main"><span class="variant">В-6</span><span class="score muted">—</span><span class="eye" title="Оценка скрыта">○</span></div>
<div class="cell-sub"><a class="sub-link" href="#">сдача ↓ 1</a><span class="st active">защита: идёт · 03:07</span></div>
</td>
<td data-label="ЛР 3 · Деревья поиска">
<div class="cell-main"><span class="score muted">—</span></div>
<div class="cell-sub"><span class="st">вариант не выдан</span></div>
</td>
<td data-label="Пароль"><span class="badge ok">изменён</span></td>
<td class="ta-r" data-label="Действия"><button class="btn btn-secondary btn-sm">Сбросить</button> <button class="btn btn-sm btn-danger">Отчислить</button></td>
</tr>
</tbody>
</table>
</div>
</div>
<section class="components">
<p class="kicker">Элементы интерфейса</p>
<div class="row">
<button class="btn">Сохранить всё</button>
<button class="btn btn-secondary">Отмена</button>
<button class="btn btn-danger">Удалить</button>
</div>
<div class="row">
<input class="input" style="width:220px" value="Поле ввода">
<input class="input" style="width:130px" value="552314">
<button class="btn btn-accent-soft btn-sm">Выдать код</button>
</div>
<p class="kicker" style="margin-top:22px">Статусы: бейдж — только индикатор, действие — отдельной кнопкой</p>
<div class="row status-line">
<span class="name">ЛР 1 · Анализ данных</span>
<span class="badge ok">Опубликована</span>
<button class="btn btn-secondary btn-sm">Скрыть</button>
</div>
<div class="row status-line">
<span class="name">ЛР 2 · Сортировки</span>
<span class="badge warn">Черновик</span>
<button class="btn btn-accent-soft btn-sm">Опубликовать</button>
</div>
<div class="row status-line">
<span class="name">Глава 3 · Жадные алгоритмы</span>
<span class="badge">Скрыто</span>
<button class="btn btn-accent-soft btn-sm">Опубликовать</button>
</div>
<p class="kicker" style="margin-top:22px">Уведомления</p>
<div class="notice ok" style="margin-bottom:8px">Оценки опубликованы всей группе — студенты видят баллы и комментарии.</div>
<div class="notice warn" style="margin-bottom:8px">Срок сдачи ЛР 2 истекает завтра в 23:59 МСК.</div>
<div class="notice err" style="margin-bottom:8px">Не удалось сохранить файлы решения — повторите попытку.</div>
<div class="notice info">Новый студент добавлен: логин 2025031, пароль показан один раз.</div>
</section>
</main>
<footer class="page-footer">LMS · демо-шаблон · стиль Б2</footer>
<script>
function setAccent(name, btn) {
document.documentElement.setAttribute('data-accent', name);
document.querySelectorAll('.swatch').forEach(function (s) { s.classList.remove('active'); });
btn.classList.add('active');
}
</script>
</body>
</html>