修改样式

This commit is contained in:
2026-07-24 17:43:44 +08:00 Unverified
parent b0eaa20da6
commit 1d89687302
3 changed files with 347 additions and 215 deletions
+1
View File
@@ -31,6 +31,7 @@ declare module 'vue' {
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
+244 -181
View File
@@ -1,59 +1,151 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import {
Collection,
DataAnalysis,
OfficeBuilding,
Operation,
Reading,
Tickets,
Calendar,
CircleCheck,
DocumentChecked,
AlarmClock,
RefreshRight,
Medal,
User,
UserFilled,
} from '@element-plus/icons-vue'
import { Operation } from '@element-plus/icons-vue'
import { useAuthStore } from '../stores/auth'
interface NavigationItem {
path: string
label: string
}
interface NavigationGroup {
key: string
label: string
direct?: boolean
items: NavigationItem[]
}
const route = useRoute()
const router = useRouter()
const auth = useAuthStore()
const collapsed = ref(false)
const mobileMenu = ref(false)
const roles = computed(() => auth.user?.roles ?? [])
const isStudent = computed(() => roles.value.includes('Student'))
const isTeacher = computed(() => roles.value.includes('Teacher'))
const isTeachingAdmin = computed(() =>
roles.value.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin'].includes(role)),
)
function hasAnyRole(allowedRoles: string[]) {
return roles.value.some((role) => allowedRoles.includes(role))
}
function whenVisible(visible: boolean, item: NavigationItem) {
return visible ? [item] : []
}
const navigationGroups = computed<NavigationGroup[]>(() => [
{
key: 'overview',
label: '教务总览',
direct: true,
items: [{ path: '/dashboard', label: '教务总览' }],
},
{
key: 'organization',
label: '组织与权限',
items: [
...whenVisible(
hasAnyRole(['SuperAdmin', 'AcademicAdmin']),
{ path: '/base-data', label: '基础数据' },
),
...whenVisible(
hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Counselor']),
{ path: '/personnel', label: '人员档案' },
),
...whenVisible(auth.isSuperAdmin, { path: '/users', label: '用户与权限' }),
],
},
{
key: 'teaching-construction',
label: '教学建设',
items: [
{ path: '/courses', label: '课程库' },
...whenVisible(isTeachingAdmin.value, { path: '/curriculum', label: '培养方案' }),
...whenVisible(isTeachingAdmin.value, { path: '/teaching-tasks', label: '教学任务' }),
...whenVisible(
hasAnyRole(['SuperAdmin', 'AcademicAdmin']),
{ path: '/schedules', label: '排课与课表' },
),
],
},
{
key: 'teaching-operation',
label: '教学运行',
items: [
...whenVisible(
hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Student']),
{
path: '/course-selections',
label: isTeachingAdmin.value ? '选课管理' : '学生选课',
},
),
...whenVisible(
hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Teacher', 'Student']),
{ path: '/grades', label: isStudent.value ? '学业成绩' : '成绩管理' },
),
...whenVisible(
hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'Teacher', 'Student']),
{
path: '/exams',
label: isStudent.value ? '我的考试' : isTeacher.value ? '我的监考' : '考试管理',
},
),
],
},
{
key: 'student-status',
label: '学籍管理',
items: [
...whenVisible(
hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Counselor', 'Student']),
{ path: '/student-status-changes', label: isStudent.value ? '学籍异动' : '异动审核' },
),
],
},
{
key: 'graduation',
label: '毕业管理',
items: [
...whenVisible(
hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Student']),
{ path: '/graduation-audits', label: isStudent.value ? '毕业资格' : '毕业审核' },
),
...whenVisible(
hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Student']),
{ path: '/degree-awards', label: isStudent.value ? '学位结果' : '学位授予' },
),
...whenVisible(
hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Counselor', 'Student']),
{ path: '/graduation-clearance', label: isStudent.value ? '毕业离校' : '离校办理' },
),
],
},
].filter((group) => group.items.length > 0))
const activeGroupKey = computed(
() => navigationGroups.value.find((group) =>
group.items.some((item) => item.path === route.path),
)?.key ?? '',
)
const workspaceLabel = computed(() => {
const roles = auth.user?.roles ?? []
if (roles.includes('SuperAdmin')) return '系统全域管理'
if (roles.includes('AcademicAdmin')) return '校级教务管理'
if (roles.includes('CollegeAdmin')) return '学院教务管理'
if (roles.includes('Counselor')) return '辅导员班级工作'
if (roles.includes('Teacher')) return '教师教学工作'
if (roles.includes('Student')) return '学生学业服务'
if (roles.value.includes('SuperAdmin')) return '系统全域管理'
if (roles.value.includes('AcademicAdmin')) return '校级教务管理'
if (roles.value.includes('CollegeAdmin')) return '学院教务管理'
if (roles.value.includes('Counselor')) return '辅导员班级工作'
if (roles.value.includes('Teacher')) return '教师教学工作'
if (roles.value.includes('Student')) return '学生学业服务'
return '教务工作台'
})
const pageTitle = computed(() => {
const titles: Record<string, string> = {
dashboard: '教务总览',
'base-data': '基础数据',
personnel: '人员档案',
courses: '课程库',
curriculum: '培养方案',
'teaching-tasks': '教学任务',
schedules: '排课与课表',
'course-selections': '选课与教学班',
grades: '成绩与学业档案',
exams: '考试与考场',
'student-status-changes': '学籍异动',
'graduation-audits': '毕业资格审核',
'degree-awards': '学位授予审核',
'graduation-clearance': '毕业离校办理',
users: '用户与权限',
}
return titles[String(route.name)] ?? '教务管理'
const matchedItem = navigationGroups.value
.flatMap((group) => group.items)
.find((item) => item.path === route.path)
return matchedItem?.label ?? '教务管理'
})
function signOut() {
@@ -65,148 +157,33 @@ onMounted(() => auth.refresh().catch(() => undefined))
</script>
<template>
<div class="shell" :class="{ 'is-collapsed': collapsed }">
<aside class="sidebar" :class="{ 'is-mobile-open': mobileMenu }">
<div class="brand">
<div class="brand-mark" aria-hidden="true">
<span v-for="index in 9" :key="index" />
</div>
<div v-if="!collapsed">
<strong>明序教务</strong>
<small>ACADEMIC OFFICE</small>
</div>
</div>
<div v-if="!collapsed" class="term-stamp">
<span>当前工作区</span>
<b>{{ workspaceLabel }}</b>
</div>
<el-menu
router
:default-active="route.path"
:collapse="collapsed"
class="nav-menu"
@select="mobileMenu = false"
>
<el-menu-item index="/dashboard">
<el-icon><DataAnalysis /></el-icon>
<template #title>教务总览</template>
</el-menu-item>
<el-menu-item
v-if="auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin'].includes(role))"
index="/base-data"
<div class="shell">
<header class="app-header">
<div class="topbar">
<button
class="menu-toggle mobile-only"
type="button"
aria-label="打开导航菜单"
@click="mobileMenu = true"
>
<el-icon><OfficeBuilding /></el-icon>
<template #title>基础数据</template>
</el-menu-item>
<el-menu-item
v-if="auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Counselor'].includes(role))"
index="/personnel"
>
<el-icon><UserFilled /></el-icon>
<template #title>人员档案</template>
</el-menu-item>
<el-menu-item index="/courses">
<el-icon><Collection /></el-icon>
<template #title>课程库</template>
</el-menu-item>
<el-menu-item
v-if="auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin'].includes(role))"
index="/curriculum"
>
<el-icon><Reading /></el-icon>
<template #title>培养方案</template>
</el-menu-item>
<el-menu-item
v-if="auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin'].includes(role))"
index="/teaching-tasks"
>
<el-icon><Tickets /></el-icon>
<template #title>教学任务</template>
</el-menu-item>
<el-menu-item
v-if="auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin'].includes(role))"
index="/schedules"
>
<el-icon><Calendar /></el-icon>
<template #title>排课与课表</template>
</el-menu-item>
<el-menu-item
v-if="auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Student'].includes(role))"
index="/course-selections"
>
<el-icon><CircleCheck /></el-icon>
<template #title>{{ auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin'].includes(role)) ? '选课管理' : '学生选课' }}</template>
</el-menu-item>
<el-menu-item
v-if="auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Teacher', 'Student'].includes(role))"
index="/grades"
>
<el-icon><DocumentChecked /></el-icon>
<template #title>{{ auth.user?.roles.includes('Student') ? '学业成绩' : '成绩管理' }}</template>
</el-menu-item>
<el-menu-item
v-if="auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'Teacher', 'Student'].includes(role))"
index="/exams"
>
<el-icon><AlarmClock /></el-icon>
<template #title>{{ auth.user?.roles.includes('Student') ? '我的考试' : auth.user?.roles.includes('Teacher') ? '我的监考' : '考试管理' }}</template>
</el-menu-item>
<el-menu-item
v-if="auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Counselor', 'Student'].includes(role))"
index="/student-status-changes"
>
<el-icon><RefreshRight /></el-icon>
<template #title>{{ auth.user?.roles.includes('Student') ? '学籍异动' : '异动审核' }}</template>
</el-menu-item>
<el-menu-item
v-if="auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Student'].includes(role))"
index="/graduation-audits"
>
<el-icon><Medal /></el-icon>
<template #title>{{ auth.user?.roles.includes('Student') ? '毕业资格' : '毕业审核' }}</template>
</el-menu-item>
<el-menu-item
v-if="auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Student'].includes(role))"
index="/degree-awards"
>
<el-icon><Medal /></el-icon>
<template #title>{{ auth.user?.roles.includes('Student') ? '学位结果' : '学位授予' }}</template>
</el-menu-item>
<el-menu-item
v-if="auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Counselor', 'Student'].includes(role))"
index="/graduation-clearance"
>
<el-icon><CircleCheck /></el-icon>
<template #title>{{ auth.user?.roles.includes('Student') ? '毕业离校' : '离校办理' }}</template>
</el-menu-item>
<el-menu-item v-if="auth.isSuperAdmin" index="/users">
<el-icon><User /></el-icon>
<template #title>用户与权限</template>
</el-menu-item>
</el-menu>
<div v-if="!collapsed" class="phase-note">
<span>第一阶段 · 核心可用版</span>
<p>教学运行学籍与毕业审核已就绪</p>
</div>
</aside>
<div v-if="mobileMenu" class="mobile-mask" @click="mobileMenu = false" />
<main class="main-area">
<header class="topbar">
<button class="menu-toggle desktop-only" type="button" @click="collapsed = !collapsed">
<el-icon><Operation /></el-icon>
</button>
<button class="menu-toggle mobile-only" type="button" @click="mobileMenu = true">
<el-icon><Operation /></el-icon>
</button>
<div class="page-heading">
<span>教务工作台</span>
<h1>{{ pageTitle }}</h1>
<div class="brand top-brand">
<div class="brand-mark" aria-hidden="true">
<span v-for="index in 9" :key="index" />
</div>
<div>
<strong>明序教务</strong>
<small>ACADEMIC OFFICE</small>
</div>
</div>
<div class="workspace-context">
<span>{{ workspaceLabel }}</span>
<b>{{ pageTitle }}</b>
</div>
<div class="user-block">
<div class="avatar">{{ auth.user?.displayName?.slice(0, 1) ?? '管' }}</div>
<div class="user-copy">
@@ -215,7 +192,93 @@ onMounted(() => auth.refresh().catch(() => undefined))
</div>
<el-button text @click="signOut">退出</el-button>
</div>
</header>
</div>
<nav class="desktop-navigation desktop-only" aria-label="主导航">
<el-menu
router
mode="horizontal"
menu-trigger="click"
:default-active="route.path"
:ellipsis="false"
class="horizontal-menu"
>
<template v-for="group in navigationGroups" :key="group.key">
<el-menu-item v-if="group.direct" :index="group.items[0].path">
{{ group.label }}
</el-menu-item>
<el-sub-menu v-else :index="group.key">
<template #title>{{ group.label }}</template>
<el-menu-item
v-for="item in group.items"
:key="item.path"
:index="item.path"
>
{{ item.label }}
</el-menu-item>
</el-sub-menu>
</template>
</el-menu>
<div class="navigation-status">
<span>CORE SYSTEM</span>
<b>核心业务已就绪</b>
</div>
</nav>
</header>
<aside class="mobile-navigation" :class="{ 'is-mobile-open': mobileMenu }">
<div class="mobile-navigation-head">
<div class="brand">
<div class="brand-mark" aria-hidden="true">
<span v-for="index in 9" :key="index" />
</div>
<div>
<strong>明序教务</strong>
<small>ACADEMIC OFFICE</small>
</div>
</div>
<button type="button" aria-label="关闭导航菜单" @click="mobileMenu = false">×</button>
</div>
<div class="term-stamp">
<span>当前工作区</span>
<b>{{ workspaceLabel }}</b>
</div>
<el-menu
router
:default-active="route.path"
:default-openeds="activeGroupKey ? [activeGroupKey] : []"
unique-opened
class="mobile-nav-menu"
@select="mobileMenu = false"
>
<template v-for="group in navigationGroups" :key="group.key">
<el-menu-item v-if="group.direct" :index="group.items[0].path">
{{ group.label }}
</el-menu-item>
<el-sub-menu v-else :index="group.key">
<template #title>{{ group.label }}</template>
<el-menu-item
v-for="item in group.items"
:key="item.path"
:index="item.path"
>
{{ item.label }}
</el-menu-item>
</el-sub-menu>
</template>
</el-menu>
<div class="phase-note">
<span>第一阶段 · 核心可用版</span>
<p>教学运行学籍与毕业审核已就绪</p>
</div>
</aside>
<div v-if="mobileMenu" class="mobile-mask" @click="mobileMenu = false" />
<main class="main-area">
<section class="content">
<RouterView />
</section>
+102 -34
View File
@@ -24,49 +24,112 @@ button, input, textarea, select { font: inherit; }
button { cursor: pointer; }
#app { min-height: 100vh; }
.shell { min-height: 100vh; display: grid; grid-template-columns: 256px 1fr; transition: grid-template-columns .2s ease; }
.shell.is-collapsed { grid-template-columns: 72px 1fr; }
.sidebar {
position: fixed; inset: 0 auto 0 0; width: 256px; z-index: 20; overflow: hidden;
display: flex; flex-direction: column; color: #eef2ff;
background:
linear-gradient(rgba(255,255,255,.025) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.025) 1px, transparent 1px),
var(--indigo-deep);
background-size: 26px 26px;
transition: width .2s ease, transform .25s ease;
.shell { min-height: 100vh; }
.app-header {
position: sticky; top: 0; z-index: 20;
box-shadow: 0 5px 18px rgba(19,34,73,.08);
}
.is-collapsed .sidebar { width: 72px; }
.brand { height: 86px; padding: 0 24px; display: flex; align-items: center; gap: 13px; border-bottom: 1px solid rgba(255,255,255,.1); white-space: nowrap; }
.is-collapsed .brand { padding: 0 17px; }
.topbar {
height: 70px; padding: 0 32px; display: flex; align-items: center; gap: 24px;
color: white; background:
linear-gradient(90deg, rgba(255,255,255,.025) 1px, transparent 1px),
linear-gradient(112deg, #152550 0%, #1d326c 72%, #176a70 130%);
background-size: 28px 28px, auto;
}
.brand {
height: 70px; display: flex; align-items: center; gap: 13px;
white-space: nowrap;
}
.top-brand { min-width: 224px; padding-right: 28px; border-right: 1px solid rgba(255,255,255,.14); }
.brand strong { display: block; font-family: "STZhongsong", "Songti SC", serif; font-size: 21px; letter-spacing: .12em; color: white; }
.brand small { display: block; margin-top: 3px; font: 9px/1.2 Consolas, monospace; letter-spacing: .16em; color: #9faad1; }
.brand-mark { flex: 0 0 auto; width: 31px; height: 31px; display: grid; grid-template-columns: repeat(3,1fr); gap: 3px; }
.brand-mark span { border: 1px solid #9eabd8; }
.brand-mark span:nth-child(2), .brand-mark span:nth-child(5), .brand-mark span:nth-child(8) { background: #3ec1ae; border-color: #3ec1ae; }
.brand-mark.large { width: 42px; height: 42px; gap: 4px; }
.workspace-context { min-width: 180px; display: grid; gap: 4px; }
.workspace-context span { color: #aeb9d8; font-size: 10px; letter-spacing: .08em; }
.workspace-context b { font-size: 14px; font-weight: 550; letter-spacing: .04em; }
.user-block { margin-left: auto; display: flex; align-items: center; gap: 11px; }
.avatar { width: 36px; height: 36px; display: grid; place-items: center; border-radius: 50%; color: #123b3e; background: #59d3c1; font-weight: 750; }
.user-copy b, .user-copy span { display: block; }
.user-copy b { color: white; font-size: 13px; }
.user-copy span { margin-top: 3px; color: #aeb9d8; font-size: 10px; }
.topbar .user-block .el-button { color: #d4daf0; }
.topbar .user-block .el-button:hover { color: white; background: rgba(255,255,255,.08); }
.desktop-navigation {
height: 51px; padding: 0 32px; display: flex; align-items: stretch;
border-bottom: 1px solid var(--line); background: rgba(255,255,255,.98);
}
.horizontal-menu.el-menu {
flex: 1; min-width: 0; height: 50px; border-bottom: none;
background: transparent;
}
.horizontal-menu.el-menu--horizontal > .el-menu-item,
.horizontal-menu.el-menu--horizontal > .el-sub-menu .el-sub-menu__title {
height: 50px; padding: 0 20px; border-bottom: 3px solid transparent;
color: #333d50; font-size: 13px;
}
.horizontal-menu.el-menu--horizontal > .el-menu-item:hover,
.horizontal-menu.el-menu--horizontal > .el-sub-menu .el-sub-menu__title:hover {
color: var(--indigo); background: #f6f8fb;
}
.horizontal-menu.el-menu--horizontal > .el-menu-item.is-active,
.horizontal-menu.el-menu--horizontal > .el-sub-menu.is-active .el-sub-menu__title {
color: var(--indigo); border-bottom-color: var(--teal);
background: linear-gradient(180deg, #fff, #f5faf9);
font-weight: 650;
}
.horizontal-menu .el-sub-menu__icon-arrow { margin-left: 6px; }
.navigation-status {
flex: 0 0 auto; min-width: 152px; padding-left: 24px;
display: grid; align-content: center; gap: 2px;
border-left: 1px solid var(--line);
}
.navigation-status span { color: var(--teal); font: 700 8px/1.2 Consolas, monospace; letter-spacing: .13em; }
.navigation-status b { color: #677083; font-size: 10px; font-weight: 500; }
.mobile-navigation {
position: fixed; inset: 0 auto 0 0; z-index: 30; width: 286px;
display: flex; flex-direction: column; overflow-y: auto;
color: #eef2ff; background:
linear-gradient(rgba(255,255,255,.025) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.025) 1px, transparent 1px),
var(--indigo-deep);
background-size: 26px 26px;
transform: translateX(-100%); transition: transform .25s ease;
}
.mobile-navigation.is-mobile-open { transform: translateX(0); }
.mobile-navigation-head {
min-height: 70px; padding: 0 18px 0 20px; display: flex; align-items: center;
justify-content: space-between; border-bottom: 1px solid rgba(255,255,255,.1);
}
.mobile-navigation-head button {
width: 34px; height: 34px; border: 1px solid rgba(255,255,255,.17);
border-radius: 6px; color: #cbd3ea; background: rgba(255,255,255,.05);
font-size: 23px; line-height: 1;
}
.term-stamp { margin: 24px 20px 11px; padding: 13px 15px; border-left: 2px solid #d89b42; background: rgba(255,255,255,.06); }
.term-stamp span { display: block; font-size: 11px; color: #aab3d4; }
.term-stamp b { display: block; margin-top: 5px; font-size: 13px; font-weight: 500; color: white; }
.nav-menu.el-menu { border: none; background: transparent; padding: 8px 10px; }
.nav-menu .el-menu-item { height: 48px; margin: 4px 0; border-radius: 7px; color: #bbc4e2; }
.nav-menu .el-menu-item:hover { color: white; background: rgba(255,255,255,.07); }
.nav-menu .el-menu-item.is-active { color: white; background: #2d478d; box-shadow: inset 3px 0 #46c6b5; }
.nav-menu .el-icon { font-size: 18px; }
.mobile-nav-menu.el-menu { border: none; background: transparent; padding: 8px 10px; }
.mobile-nav-menu .el-menu-item,
.mobile-nav-menu .el-sub-menu__title {
height: 45px; margin: 2px 0; border-radius: 6px; color: #bbc4e2; background: transparent;
}
.mobile-nav-menu .el-menu-item:hover,
.mobile-nav-menu .el-sub-menu__title:hover { color: white; background: rgba(255,255,255,.07); }
.mobile-nav-menu .el-menu-item.is-active { color: white; background: #2d478d; box-shadow: inset 3px 0 #46c6b5; }
.mobile-nav-menu .el-sub-menu .el-menu { background: rgba(0,0,0,.1); }
.mobile-nav-menu .el-sub-menu .el-menu-item { min-width: 0; padding-left: 40px !important; font-size: 12px; }
.phase-note { margin: auto 20px 24px; padding-top: 17px; border-top: 1px solid rgba(255,255,255,.12); }
.phase-note span { color: #45c6b5; font-size: 11px; font-weight: 700; letter-spacing: .06em; }
.phase-note p { margin: 7px 0 0; color: #9faad0; font-size: 12px; line-height: 1.6; }
.main-area { grid-column: 2; min-width: 0; }
.topbar { height: 86px; padding: 0 32px; display: flex; align-items: center; gap: 20px; background: rgba(255,255,255,.96); border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 10; backdrop-filter: blur(10px); }
.menu-toggle { width: 38px; height: 38px; border: 1px solid var(--line); border-radius: 7px; color: var(--indigo); background: white; }
.page-heading span { display: block; color: #9aa1af; font-size: 10px; letter-spacing: .14em; text-transform: uppercase; }
.page-heading h1 { margin: 3px 0 0; font-size: 20px; font-weight: 650; letter-spacing: .04em; }
.user-block { margin-left: auto; display: flex; align-items: center; gap: 11px; }
.avatar { width: 38px; height: 38px; display: grid; place-items: center; border-radius: 50%; color: white; background: var(--teal); font-weight: 700; }
.user-copy b, .user-copy span { display: block; }
.user-copy b { font-size: 13px; }
.user-copy span { margin-top: 3px; color: var(--muted); font-size: 11px; }
.main-area { min-width: 0; }
.menu-toggle {
width: 38px; height: 38px; border: 1px solid rgba(255,255,255,.18);
border-radius: 7px; color: white; background: rgba(255,255,255,.07);
}
.content { padding: 28px 32px 48px; max-width: 1540px; margin: 0 auto; }
.mobile-only { display: none; }
@@ -795,15 +858,20 @@ button { cursor: pointer; }
}
@media (max-width: 980px) {
.shell, .shell.is-collapsed { display: block; }
.sidebar, .is-collapsed .sidebar { width: 256px; transform: translateX(-100%); }
.sidebar.is-mobile-open { transform: translateX(0); }
.app-header { box-shadow: 0 3px 14px rgba(19,34,73,.1); }
.main-area { width: 100%; }
.mobile-mask { position: fixed; inset: 0; z-index: 15; background: rgba(13,24,48,.45); }
.mobile-mask { position: fixed; inset: 0; z-index: 25; background: rgba(13,24,48,.52); }
.desktop-only { display: none; }
.mobile-only { display: inline-grid; place-items: center; }
.topbar { height: 72px; padding: 0 16px; }
.topbar { height: 66px; padding: 0 16px; gap: 13px; }
.top-brand { min-width: 0; padding-right: 0; border-right: none; }
.top-brand .brand-mark { width: 27px; height: 27px; }
.top-brand strong { font-size: 17px; }
.top-brand small { display: none; }
.workspace-context { display: none; }
.user-copy { display: none; }
.avatar { width: 32px; height: 32px; font-size: 12px; }
.topbar .user-block { gap: 4px; }
.content { padding: 18px 14px 35px; }
.term-hero { min-height: 220px; padding: 25px; flex-direction: column; align-items: flex-start; justify-content: flex-end; }
.term-hero button { margin: 22px 0 0; width: 100%; }