diff --git a/web/src/main/resources/static/css/app.css b/web/src/main/resources/static/css/app.css index 3307f3a..12d004e 100644 --- a/web/src/main/resources/static/css/app.css +++ b/web/src/main/resources/static/css/app.css @@ -194,52 +194,6 @@ p { margin-top: 0; } .toolbar { display: flex; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap; } .stat-value { margin: 0.5rem 0 0; font-size: 1.875rem; font-weight: 600; } .stat-title { font-size: 0.875rem; color: var(--c-text-muted); margin: 0; } -/* ---- Utilities: плоские классы поверх компонентов (в духе Bootstrap) ---- */ -.flex { display: flex; } -.flex-wrap { flex-wrap: wrap; } -.items-center { align-items: center; } -.items-start { align-items: flex-start; } -.justify-between { justify-content: space-between; } -.justify-center { justify-content: center; } -.m-0 { margin: 0; } -.mt-1 { margin-top: 0.25rem; } -.mt-2 { margin-top: 0.5rem; } -.mt-4 { margin-top: 1rem; } -.mt-6 { margin-top: 1.5rem; } -.mt-8 { margin-top: 2rem; } -.mb-0 { margin-bottom: 0; } -.mb-1 { margin-bottom: 0.25rem; } -.mb-2 { margin-bottom: 0.5rem; } -.mb-4 { margin-bottom: 1rem; } -.mb-6 { margin-bottom: 1.5rem; } -.mb-8 { margin-bottom: 2rem; } -.ml-1 { margin-left: 0.25rem; } -.ml-2 { margin-left: 0.5rem; } -.ml-auto { margin-left: auto; } -.p-0 { padding: 0; } -.p-4 { padding: 1rem; } -.p-6 { padding: 1.5rem; } -.px-6 { padding-left: 1.5rem; padding-right: 1.5rem; } -.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; } -.gap-1 { gap: 0.25rem; } -.gap-2 { gap: 0.5rem; } -.gap-3 { gap: 0.75rem; } -.gap-4 { gap: 1rem; } -.w-full { width: 100%; } -.w-260 { width: 260px; } -.overflow-x-auto { overflow-x: auto; } -.text-sm { font-size: 0.875rem; } -.text-muted { color: var(--c-text-muted); } -.text-strong { font-weight: 600; color: var(--c-text); } -.text-center { text-align: center; } -.text-right { text-align: right; } -.text-break { overflow-wrap: anywhere; } -.font-mono { font-family: monospace; } -.font-medium { font-weight: 500; } -.font-semibold { font-weight: 600; } -.opacity-30 { opacity: 0.3; } -.offscreen { position: fixed; top: -1000px; left: 0; } - /* Badges — «этикетки»: строгая рамка, разрядка капсом; только индикаторы статуса */ .badge { display: inline-flex; align-items: center; @@ -535,7 +489,7 @@ dialog.modal.modal-lg .modal-body { max-height: 88vh; min-height: 62vh; } background: none; cursor: pointer; color: var(--c-text-muted); } .sub-toggle:hover { color: var(--c-text); } -.sub-arrow .ico { width: 0.75rem; height: 0.75rem; } +.sub-arrow .ico { width: 1rem; height: 1rem; } .sub-arrow { transform: rotate(0deg); transition: transform 0.2s; } .sub-acc.collapsed .sub-arrow { transform: rotate(0deg); } .sub-acc:not(.collapsed) .sub-arrow { transform: rotate(180deg); } @@ -729,3 +683,74 @@ th.th-filterable { padding-bottom: 0.35rem; } } .md-video-figure { margin: 1rem 0; text-align: center; } .md-video { width: 100%; max-width: 100%; border-radius: var(--radius); background: #000; } + +/* ---- Utilities: плоские классы поверх компонентов (в духе Bootstrap) ---- */ +.flex { display: flex; } +.flex-wrap { flex-wrap: wrap; } +.items-center { align-items: center; } +.items-start { align-items: flex-start; } +.justify-between { justify-content: space-between; } +.justify-center { justify-content: center; } +.m-0 { margin: 0; } +.mt-1 { margin-top: 0.25rem; } +.mt-2 { margin-top: 0.5rem; } +.mt-4 { margin-top: 1rem; } +.mt-6 { margin-top: 1.5rem; } +.mt-8 { margin-top: 2rem; } +.mb-0 { margin-bottom: 0; } +.mb-1 { margin-bottom: 0.25rem; } +.mb-2 { margin-bottom: 0.5rem; } +.mb-4 { margin-bottom: 1rem; } +.mb-6 { margin-bottom: 1.5rem; } +.mb-8 { margin-bottom: 2rem; } +.ml-1 { margin-left: 0.25rem; } +.ml-2 { margin-left: 0.5rem; } +.ml-auto { margin-left: auto; } +.p-0 { padding: 0; } +.p-4 { padding: 1rem; } +.p-6 { padding: 1.5rem; } +.px-6 { padding-left: 1.5rem; padding-right: 1.5rem; } +.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; } +.gap-1 { gap: 0.25rem; } +.gap-2 { gap: 0.5rem; } +.gap-3 { gap: 0.75rem; } +.gap-4 { gap: 1rem; } +.w-full { width: 100%; } +.w-260 { width: 260px; } +.overflow-x-auto { overflow-x: auto; } +.text-sm { font-size: 0.875rem; } +.text-muted { color: var(--c-text-muted); } +.text-strong { font-weight: 600; color: var(--c-text); } +.text-center { text-align: center; } +.text-right { text-align: right; } +.text-break { overflow-wrap: anywhere; } +.font-mono { font-family: monospace; } +.font-medium { font-weight: 500; } +.font-semibold { font-weight: 600; } +.opacity-30 { opacity: 0.3; } +.offscreen { position: fixed; top: -1000px; left: 0; } + + +/* ---- Мобильное меню: бургер в хэдере, ссылки в выезжающем сайдбаре ---- */ +.nav-burger { display: none; } +.mobile-nav { display: none; } +.mobile-nav-overlay { display: none; } +@media (max-width: 720px) { + .nav { display: none; } + .page-header .user { display: none; } + .nav-burger { display: inline-flex; } + .mobile-nav { + display: flex; flex-direction: column; gap: 1rem; + position: fixed; top: 0; left: 0; bottom: 0; width: 17rem; max-width: 86vw; + z-index: 120; background: var(--c-surface); border-right: 1px solid var(--c-border); + padding: 1rem; overflow-y: auto; + transform: translateX(-100%); transition: transform 0.2s; + } + .mobile-nav.open { transform: none; } + .mobile-nav-overlay { display: block; position: fixed; inset: 0; z-index: 110; background: rgba(0, 0, 0, 0.45); } + .mobile-nav-head { display: flex; align-items: center; justify-content: space-between; } + .mobile-nav-links { display: flex; flex-direction: column; gap: 0.25rem; } + .mobile-nav-links .nav-link { padding: 0.5rem 0.25rem; font-size: 1rem; } + .mobile-nav-user { display: flex; flex-direction: column; gap: 0.75rem; margin-top: auto; padding-top: 1rem; border-top: 1px solid var(--c-border); } +} +html.nav-open, html.nav-open body { overflow: hidden; } diff --git a/web/src/main/resources/static/icons/chevron-down.svg b/web/src/main/resources/static/icons/chevron-down.svg index 207e627..b61866f 100644 --- a/web/src/main/resources/static/icons/chevron-down.svg +++ b/web/src/main/resources/static/icons/chevron-down.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/web/src/main/resources/static/js/app.js b/web/src/main/resources/static/js/app.js index a99111e..3ecbd49 100644 --- a/web/src/main/resources/static/js/app.js +++ b/web/src/main/resources/static/js/app.js @@ -84,17 +84,20 @@ /* Активный пункт меню */ function initNavActive() { var path = window.location.pathname; - var links = [].slice.call(document.querySelectorAll('#top-nav .nav-link')); - var best = null; + var links = [].slice.call(document.querySelectorAll('#top-nav .nav-link, #mobile-nav .nav-link')); + var bestHref = null; var bestLen = -1; links.forEach(function (a) { var href = a.getAttribute('href') || ''; if (href === '/') return; if (path === href || path.indexOf(href + '/') === 0) { - if (href.length > bestLen) { best = a; bestLen = href.length; } + if (href.length > bestLen) { bestHref = href; bestLen = href.length; } } }); - if (best) best.classList.add('active'); + if (!bestHref) return; + links.forEach(function (a) { + if (a.getAttribute('href') === bestHref) a.classList.add('active'); + }); } /* Серверные уведомления: автоскрытие и кнопка закрытия */ @@ -238,6 +241,34 @@ else if (kind === 'comment-add' && typeof window.csRow === 'function') window.csRow('', 'neutral'); }); + /* Мобильное меню */ + function openMobileNav() { + var n = document.getElementById('mobile-nav'); + var o = document.getElementById('mobile-nav-overlay'); + var b = document.getElementById('nav-burger'); + if (n) n.classList.add('open'); + if (o) o.hidden = false; + if (b) b.setAttribute('aria-expanded', 'true'); + document.documentElement.classList.add('nav-open'); + } + function closeMobileNav() { + var n = document.getElementById('mobile-nav'); + var o = document.getElementById('mobile-nav-overlay'); + var b = document.getElementById('nav-burger'); + if (n) n.classList.remove('open'); + if (o) o.hidden = true; + if (b) b.setAttribute('aria-expanded', 'false'); + document.documentElement.classList.remove('nav-open'); + } + document.addEventListener('click', function (e) { + if (e.target.closest('#nav-burger')) { openMobileNav(); return; } + if (e.target.closest('#mobile-nav-overlay') || e.target.closest('.mobile-nav-close')) { closeMobileNav(); return; } + if (e.target.closest('.mobile-nav a')) { closeMobileNav(); return; } + }); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeMobileNav(); + }); + /* Изменение полей: автоотправка форм (data-auto-submit), подтверждение занятия занятого варианта (select.js-variant-select) */ document.addEventListener('change', function (e) { diff --git a/web/src/main/resources/templates/admin/group.html b/web/src/main/resources/templates/admin/group.html index 6b262bf..9aadf08 100644 --- a/web/src/main/resources/templates/admin/group.html +++ b/web/src/main/resources/templates/admin/group.html @@ -344,7 +344,7 @@
- +