/* ============================================
   Lucerna — Avalonia-inspired Design
   ============================================ */

@font-face {
  font-family: "HarmonyOS Sans";
  src: url("HarmonyOS_Sans_SC_Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* --- Design Tokens --- */
:root {
  --bg-deep:       #0a0a14;
  --bg-primary:    #12121e;
  --bg-secondary:  #1a1a2e;
  --bg-tertiary:   #22223a;
  --bg-elevated:   #2a2a44;
  --bg-hover:      #2e2e4a;

  --accent:        #7c6cf0;
  --accent-hover:  #9588f7;
  --accent-dim:    rgba(124,108,240,0.12);
  --accent-glow:   rgba(124,108,240,0.25);

  --success:       #4ade80;
  --warning:       #fbbf24;
  --danger:        #f87171;
  --info:          #60a5fa;

  --text-primary:  #e8e6f0;
  --text-secondary:#9896a8;
  --text-tertiary: #6b697a;
  --text-disabled: #44434f;

  --border:        rgba(255,255,255,0.06);
  --border-subtle: rgba(255,255,255,0.035);
  --border-accent: rgba(124,108,240,0.35);
  --border-hover:  rgba(255,255,255,0.1);
  --border-hover-strong: rgba(255,255,255,0.12);
  --table-hover:   rgba(255,255,255,0.015);
  --highlight-ring: rgba(255,255,255,0.16);

  --radius-sm: 4px;
  --radius:    8px;
  --radius-lg: 12px;
  --radius-xl: 16px;

  --shadow-sm: 0 1px 3px rgba(0,0,0,0.3);
  --shadow:    0 4px 16px rgba(0,0,0,0.35);
  --shadow-lg: 0 8px 32px rgba(0,0,0,0.45);

  --transition: 0.18s ease;
  --font: "HarmonyOS Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
}

/* --- Light Theme --- */
[data-theme="light"] {
  --bg-deep:       #eef0f4;
  --bg-primary:    #f3f4f6;
  --bg-secondary:  #ffffff;
  --bg-tertiary:   #e8eaf0;
  --bg-elevated:   #dde0e8;
  --bg-hover:      #d3d7e0;

  --accent:        #6b5ce7;
  --accent-hover:  #7c6cf0;
  --accent-dim:    rgba(107,92,231,0.1);
  --accent-glow:   rgba(107,92,231,0.2);

  --text-primary:  #1a1a2e;
  --text-secondary:#5a5870;
  --text-tertiary: #8a8898;
  --text-disabled: #b8b6c8;

  --border:        rgba(0,0,0,0.08);
  --border-subtle: rgba(0,0,0,0.05);
  --border-accent: rgba(107,92,231,0.35);
  --border-hover:  rgba(0,0,0,0.1);
  --border-hover-strong: rgba(0,0,0,0.12);
  --table-hover:   rgba(0,0,0,0.02);
  --highlight-ring: rgba(0,0,0,0.15);

  --shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
  --shadow:    0 4px 16px rgba(0,0,0,0.1);
  --shadow-lg: 0 8px 32px rgba(0,0,0,0.12);
}

/* --- Reset & Base --- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html {
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.6;
  color: var(--text-primary);
  background: var(--bg-deep);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  min-height: 100vh;
  background: var(--bg-primary);
}

a { color: var(--accent); text-decoration: none; transition: color var(--transition); }
a:hover { color: var(--accent-hover); }

::selection { background: var(--accent-dim); color: var(--accent-hover); }

/* Focus visible */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* 点击按钮时不显示浏览器默认蓝色焦点高亮，键盘导航仍保留焦点轮廓 */
button,
[role="button"] {
  -webkit-tap-highlight-color: transparent;
}

button:focus:not(:focus-visible),
[role="button"]:focus:not(:focus-visible) {
  outline: none;
}

button:focus-visible,
[role="button"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Scrollbar */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--bg-elevated); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--bg-hover); }

/* --- Typography --- */
h1 { font-size: 1.75rem; font-weight: 700; letter-spacing: -0.02em; line-height: 1.3; }
h2 { font-size: 1.35rem; font-weight: 600; letter-spacing: -0.01em; line-height: 1.35; }
h3 { font-size: 1.1rem; font-weight: 600; line-height: 1.4; }
h4 { font-size: 0.95rem; font-weight: 600; line-height: 1.45; }

.text-secondary { color: var(--text-secondary); }
.text-tertiary  { color: var(--text-tertiary); }
.text-accent    { color: var(--accent); }
.text-success   { color: var(--success); }
.text-danger    { color: var(--danger); }
.text-warning   { color: var(--warning); }
.text-sm        { font-size: 0.85rem; }
.text-xs        { font-size: 0.78rem; }

/* --- Layout --- */
.page-centered {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 24px;
}

.container {
  width: 100%;
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 24px;
}

/* --- Card --- */
.card {
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 28px;
}

.card-elevated {
  background: var(--bg-tertiary);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 24px;
}

.card-hover {
  transition: background var(--transition), border-color var(--transition), transform var(--transition);
  cursor: pointer;
}
.card-hover:hover {
  background: var(--bg-tertiary);
  border-color: var(--border-hover);
  transform: translateY(-2px);
}

/* Auth card - centered with subtle glow */
.auth-card {
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 40px 36px;
  width: 100%;
  max-width: 420px;
  box-shadow: var(--shadow-lg), 0 0 80px rgba(124,108,240,0.04);
  position: relative;
}
.auth-card::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  opacity: 0.6;
}

.auth-header {
  text-align: center;
  margin-bottom: 32px;
}
.auth-header h1 {
  font-size: 1.5rem;
  margin-bottom: 6px;
}
.auth-header p {
  color: var(--text-secondary);
  font-size: 0.9rem;
}

.auth-logo {
  width: 48px;
  height: 48px;
  background: var(--accent-dim);
  border: 1px solid var(--border-accent);
  border-radius: var(--radius-lg);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 16px;
  font-size: 1.4rem;
}

.auth-logo img,
.topbar-brand-icon img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  border-radius: inherit;
}

/* --- Buttons --- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 18px;
  font-size: 0.9rem;
  font-weight: 500;
  font-family: var(--font);
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  transition: all var(--transition);
  white-space: nowrap;
  user-select: none;
  line-height: 1.5;
  /* 点击光晕（.btn-halo）要贴着按钮的形状铺，得有个定位祖先 */
  position: relative;
}
.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.btn-primary {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}
.btn-primary:hover:not(:disabled) {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  box-shadow: 0 0 20px var(--accent-glow);
}

/* --- Button Loading Spinner --- */
.btn .btn-spinner {
  display: none;
  width: 18px;
  height: 18px;
  border: 2px solid rgba(255,255,255,0.3);
  border-top-color: #fff;
  border-radius: 50%;
  animation: btn-spin 0.6s linear infinite;
}

.btn.loading .btn-spinner {
  display: inline-block;
}

.btn.loading .btn-text {
  display: none;
}

.btn.loading {
  pointer-events: none;
  cursor: wait;
}

/* 浅色底按钮（次要 / 幽灵 / 危险）上的转圈：白圈在浅底上看不见，改用文字色，
   但保持"淡轨道 + 实心头"的同一个样子 —— 和主按钮的转圈观感完全一致 */
.btn-secondary .btn-spinner,
.btn-ghost .btn-spinner,
.btn-danger .btn-spinner {
  border-color: currentColor;
  border-top-color: transparent; /* 不支持 color-mix 时的兜底：环 + 缺口 */
}
@supports (color: color-mix(in srgb, red 50%, transparent)) {
  .btn-secondary .btn-spinner,
  .btn-ghost .btn-spinner,
  .btn-danger .btn-spinner {
    border-color: color-mix(in srgb, currentColor 30%, transparent);
    border-top-color: currentColor;
  }
}

@keyframes btn-spin {
  to { transform: rotate(360deg); }
}
/* --- End Button Loading Spinner --- */

/* --- 沉浸光感（HarmonyOS 风格的悬停边缘光） ---
   JS（app.js 的 initCursorGlow）把光心相对每个条目的坐标写进 --glow-x / --glow-y，
   于是离光标近的边缘先亮、远处渐灭；鼠标停在条目附近（并没有悬停到）时，靠近的一侧同样会亮。
   只挂在 JS 标记过的 .cursor-glow 条目上；不依赖 background-attachment: fixed，
   在任何浏览器 / 开发者工具的模拟器里都按同一套坐标工作。 */
.cursor-glow {
  position: relative;
  /* 两套光都用光心的局部坐标做径向渐变：环形轮廓 + 表面光洒 */
  --glow-ring: radial-gradient(170px circle at var(--glow-x, -500px) var(--glow-y, -500px),
    rgba(255, 255, 255, 0.85),
    rgba(255, 255, 255, 0.28) 38%,
    transparent 72%);
  --glow-surface: radial-gradient(240px circle at var(--glow-x, -500px) var(--glow-y, -500px),
    rgba(255, 255, 255, 0.11),
    rgba(255, 255, 255, 0.035) 42%,
    transparent 72%);
}

/* 边缘环形高光：mask 只留 1.5px 的一圈轮廓。
   注意：mask 简写会把 mask-composite 重置回 add，简写必须写在 composite 之前 */
.cursor-glow::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.5px;
  pointer-events: none;
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
          mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  background: var(--glow-ring);
  opacity: var(--glow-strength, 0);
  transition: opacity 0.35s ease;
}

/* 光洒到表面：同光心的柔和一层，光感不只停在轮廓上 */
.cursor-glow::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: var(--glow-surface);
  opacity: var(--glow-strength, 0);
  transition: opacity 0.35s ease;
}

/* 表格行（后台的用户 / 文件 / 登录信息）：表面光直接画在行自身的背景上 ——
   行背景在单元格之下，而单元格默认透明，所以能透出来。这样即使浏览器不在
   <tr> 上生成伪元素，光感也一定看得见；轮廓仍由上面通用的 ::after 负责 */
.table tbody tr.cursor-glow {
  background-image: var(--glow-surface);
}
.table tbody tr.cursor-glow::before {
  content: none; /* 避免和行背景上的光叠成两层 */
}

/* 浅色主题：白光在浅底上看不见，换成主题色的光 */
[data-theme="light"] .cursor-glow {
  --glow-ring: radial-gradient(170px circle at var(--glow-x, -500px) var(--glow-y, -500px),
    rgba(124, 110, 245, 0.72),
    rgba(124, 110, 245, 0.22) 38%,
    transparent 72%);
  --glow-surface: radial-gradient(240px circle at var(--glow-x, -500px) var(--glow-y, -500px),
    rgba(124, 110, 245, 0.09),
    rgba(124, 110, 245, 0.03) 42%,
    transparent 72%);
}

/* 不支持环形 mask 的浏览器不画轮廓层，避免糊成一片白 */
@supports not ((mask-composite: exclude) or (-webkit-mask-composite: xor)) {
  .cursor-glow::after {
    content: none;
  }
}

/* --- 点击光晕（按下按钮后从中心向外扩散的一圈光） ---
   JS（app.js 的 spawnButtonHalo）在点击时往按钮里插一层 .btn-halo：
   这一层就是按钮的形状 —— inset: 0 + border-radius: inherit + overflow: hidden，
   里面的圆环由它的 ::after 画、一路往外长，超出按钮的部分被这层裁掉。
   于是光晕看着始终只在按钮的轮廓里扩散，走到边缘时同时淡出，不是被硬切一刀。
   终点直径由 JS 按按钮对角线写进 --halo-size：环要走到离中心最远的那个角。 */
.btn-halo {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  overflow: hidden;
  pointer-events: none;
  /* 深色主题用白光；浅色主题的覆盖在下面（白光落在浅底上看不见） */
  --halo-ring: rgba(255, 255, 255, 0.85);
  --halo-glow: rgba(255, 255, 255, 0.42);
  --halo-wash: rgba(255, 255, 255, 0.13);
}

.btn-halo::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  border: 1.5px solid var(--halo-ring);
  /* 环内跟着带一点淡光：扩散的不只是"一根线"，而是往前推的一团光 */
  background: radial-gradient(circle, var(--halo-wash), transparent 70%);
  box-shadow: 0 0 12px var(--halo-glow), inset 0 0 10px var(--halo-glow);
  animation: btn-halo-spread 0.62s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}

@keyframes btn-halo-spread {
  0%   { width: 0; height: 0; opacity: 0.9; }
  62%  { opacity: 0.7; }
  100% { width: var(--halo-size, 140px); height: var(--halo-size, 140px); opacity: 0; }
}

/* 浅色主题：与沉浸光感同一套换色逻辑，白光换成主题色 */
[data-theme="light"] .btn-halo {
  --halo-ring: rgba(124, 110, 245, 0.72);
  --halo-glow: rgba(124, 110, 245, 0.32);
  --halo-wash: rgba(124, 110, 245, 0.10);
}
/* 底色本身就是主题色的按钮（主按钮 / 强调按钮）：浅色主题下紫环落在紫底上同样看不见，
   这两类保持白光 */
[data-theme="light"] .btn-primary .btn-halo,
[data-theme="light"] .btn-accent .btn-halo {
  --halo-ring: rgba(255, 255, 255, 0.9);
  --halo-glow: rgba(255, 255, 255, 0.4);
  --halo-wash: rgba(255, 255, 255, 0.14);
}

.btn-secondary {
  background: var(--bg-tertiary);
  color: var(--text-primary);
  border-color: var(--border);
}
.btn-secondary:hover:not(:disabled) {
  background: var(--bg-elevated);
  border-color: var(--border-hover-strong);
}

.btn-ghost {
  background: transparent;
  color: var(--text-secondary);
  border-color: transparent;
}
.btn-ghost:hover:not(:disabled) {
  background: var(--bg-tertiary);
  color: var(--text-primary);
}

.btn-danger {
  background: rgba(248,113,113,0.12);
  color: var(--danger);
  border-color: rgba(248,113,113,0.2);
}
.btn-danger:hover:not(:disabled) {
  background: rgba(248,113,113,0.2);
  border-color: rgba(248,113,113,0.35);
}

.btn-sm {
  padding: 5px 12px;
  font-size: 0.82rem;
}

.btn-lg {
  padding: 11px 28px;
  font-size: 1rem;
}

.btn-block {
  width: 100%;
}

.btn-icon {
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: var(--radius);
}

/* --- Form Elements --- */
.form-group {
  margin-bottom: 18px;
}

.form-label {
  display: block;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--text-secondary);
  margin-bottom: 6px;
}

.form-input, .form-select {
  width: 100%;
  padding: 9px 14px;
  font-size: 0.9rem;
  font-family: var(--font);
  color: var(--text-primary);
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: border-color var(--transition), box-shadow var(--transition);
  outline: none;
}
.form-input::placeholder { color: var(--text-tertiary); }
.form-input:focus, .form-select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-dim);
}
.form-input.error {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px rgba(248,113,113,0.12);
}

.form-select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%239896a8' d='M2 4l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding-right: 36px;
  cursor: pointer;
}

.form-hint {
  font-size: 0.78rem;
  color: var(--text-tertiary);
  margin-top: 4px;
}

.form-error {
  font-size: 0.78rem;
  color: var(--danger);
  margin-top: 4px;
}

.form-row {
  display: flex;
  gap: 12px;
}
.form-row .form-group { flex: 1; }

/* --- Badges --- */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  font-size: 0.75rem;
  font-weight: 500;
  border-radius: 99px;
  white-space: nowrap;
}
.badge-accent {
  background: var(--accent-dim);
  color: var(--accent-hover);
  border: 1px solid var(--border-accent);
}
.badge-success {
  background: rgba(74,222,128,0.1);
  color: var(--success);
  border: 1px solid rgba(74,222,128,0.2);
}
.badge-warning {
  background: rgba(251,191,36,0.1);
  color: var(--warning);
  border: 1px solid rgba(251,191,36,0.2);
}
.badge-danger {
  background: rgba(248,113,113,0.1);
  color: var(--danger);
  border: 1px solid rgba(248,113,113,0.2);
}
.badge-info {
  background: rgba(96,165,250,0.1);
  color: var(--info);
  border: 1px solid rgba(96,165,250,0.2);
}
.badge-neutral {
  background: var(--bg-tertiary);
  color: var(--text-secondary);
  border: 1px solid var(--border);
}

/* --- Top Bar --- */
.topbar {
  height: 52px;
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  padding: 0 20px;
  gap: 16px;
  position: sticky;
  top: 0;
  z-index: 100;
}

.topbar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
  font-size: 1rem;
  color: var(--text-primary);
}

.topbar-brand-icon {
  width: 28px;
  height: 28px;
  background: var(--accent-dim);
  border: 1px solid var(--border-accent);
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
}

.topbar-spacer { flex: 1; }

/* Topbar 里的按钮（头像昵称 / 切换班级 / 退出 / 返回）统一高度，排在一条线上。
   竖直内边距清零、由 flex 居中，这样图标按钮和文字按钮高度完全一致 */
.topbar .btn {
  height: 34px;
  padding-top: 0;
  padding-bottom: 0;
}

.topbar-user {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--text-secondary);
  font-size: 0.88rem;
}

.topbar-avatar {
  width: 24px;
  height: 24px;
  background: var(--accent-dim);
  border: 1px solid var(--border-accent);
  border-radius: 50%;
  overflow: hidden;
  position: relative;
  display: block;
  font-size: 0.72rem;
  color: var(--accent);
  font-weight: 600;
  line-height: 22px; /* 减去上下边框，头像里的首字母正好居中 */
  text-align: center;
}

/* --- User Menu Dropdown --- */
.user-menu {
  position: relative;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
}
/* 顶栏「头像 + 昵称」：和「移动 / 复制」同款按钮（.btn btn-secondary btn-sm），
   沉浸光感、悬停抬升、按下反馈都会自动继承，这里只关掉触屏点击的灰色块、
   并让内部元素不干扰按钮自身的交互样式 */
.user-menu-trigger {
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.user-menu-trigger * {
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
}
.user-menu-dropdown {
  display: none;
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: 4px;
  min-width: 140px;
  background: var(--bg-tertiary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  z-index: 100;
  overflow: hidden;
}
.user-menu-dropdown.show {
  display: block;
}
.user-menu-item {
  padding: 10px 16px;
  cursor: pointer;
  transition: background var(--transition);
  font-size: 0.92rem;
  white-space: nowrap;
}
.user-menu-item:hover {
  background: var(--bg-hover);
}
.user-menu-item:not(:last-child) {
  border-bottom: 1px solid var(--border-subtle);
}


/* --- Class Selection Grid --- */
.class-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 12px;
}

.class-card {
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 24px 20px;
  text-align: center;
  cursor: pointer;
  transition: all var(--transition);
}
.class-card:hover {
  background: var(--bg-tertiary);
  border-color: var(--border-accent);
  transform: translateY(-2px);
  box-shadow: 0 4px 20px rgba(0,0,0,0.3), 0 0 30px var(--accent-dim);
}
.class-card .class-icon {
  width: 48px;
  height: 48px;
  background: var(--accent-dim);
  border: 1px solid var(--border-accent);
  border-radius: var(--radius-lg);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 12px;
  font-size: 1.3rem;
}
.class-card .class-name {
  font-weight: 600;
  font-size: 1rem;
  margin-bottom: 4px;
}

/* --- File List --- */
.file-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.file-item {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  border-radius: var(--radius);
  transition: background var(--transition), transform var(--transition), box-shadow var(--transition);
}
.file-item:hover {
  background: var(--bg-tertiary);
}

/* 悬停反馈增强：轻微抬升 + 阴影，配合沉浸光感一起作用（桌面端鼠标） */
.file-item:hover,
.folder-item:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.16);
}

.btn:hover:not(:disabled) {
  transform: translateY(-1px);
}
.btn:active:not(:disabled) {
  transform: scale(0.98);
}

.file-icon {
  width: 40px;
  height: 40px;
  background: var(--bg-tertiary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  flex-shrink: 0;
}

.file-info {
  flex: 1;
  min-width: 0;
}
.file-name {
  font-weight: 500;
  font-size: 0.92rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.file-meta {
  font-size: 0.78rem;
  color: var(--text-tertiary);
  display: flex;
  gap: 12px;
  margin-top: 2px;
}

.file-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.batch-action-bar[hidden] {
  display: none;
}
.batch-action-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin: -8px 0 14px;
  padding: 8px 12px;
  border: 1px solid var(--border-accent);
  border-radius: var(--radius);
  background: var(--bg-secondary);
}
.batch-selection-count {
  color: var(--text-secondary);
  font-size: 0.84rem;
  margin-right: 4px;
}
.batch-delete-hint {
  color: var(--danger);
  font-size: 0.78rem;
}
.batch-file-checkbox {
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 22px;
  cursor: pointer;
}
.batch-file-checkbox input {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  cursor: pointer;
}
.file-item-selected {
  background: var(--accent-dim);
  box-shadow: inset 2px 0 0 var(--accent);
}

/* --- Tabs --- */
.tabs {
  display: flex;
  gap: 2px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 20px;
}

.tab {
  padding: 10px 18px;
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: all var(--transition);
  background: none;
  border-top: none;
  border-left: none;
  border-right: none;
  font-family: var(--font);
}
.tab:hover {
  color: var(--text-primary);
  background: var(--bg-tertiary);
}
.tab.active {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* --- Table --- */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
}
.table th {
  text-align: left;
  padding: 10px 16px;
  font-weight: 600;
  font-size: 0.8rem;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  background: var(--bg-tertiary);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.table td {
  padding: 10px 16px;
  border-bottom: 1px solid var(--border-subtle);
  vertical-align: middle;
}
.table tr:last-child td { border-bottom: none; }
.table tr:hover td { background: var(--table-hover); }
.table-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

/* --- Pagination --- */
.login-log-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 16px;
}
.login-log-count {
  font-size: 0.85rem;
  color: var(--text-tertiary);
}
.pagination-controls {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}
.pagination-btn {
  min-width: 30px;
  height: 30px;
  padding: 0 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-secondary);
  font-size: 0.85rem;
  cursor: pointer;
  transition: all var(--transition);
}
.pagination-btn:hover:not(:disabled):not(.active) {
  border-color: var(--accent);
  color: var(--accent);
}
.pagination-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.pagination-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* --- Modal --- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.6);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}
.modal-overlay.active {
  opacity: 1;
  pointer-events: all;
}


.folder-selector-modal-overlay {
  z-index: 1100;
}

.modal {
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 28px;
  width: 90%;
  max-width: 480px;
  max-height: 85vh;
  overflow-y: auto;
  box-shadow: var(--shadow-lg);
  transform: translateY(10px) scale(0.98);
  transition: transform 0.2s ease;
}
.modal-overlay.active .modal {
  transform: translateY(0) scale(1);
}

.modal-sm {
  max-width: 400px;
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 20px;
}
.modal-header h3 {
  font-size: 1.1rem;
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 24px;
}


/* --- Role selector --- */
.role-selector-trigger {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  color: var(--text-primary);
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--transition), box-shadow var(--transition), background var(--transition);
}
.role-selector-trigger:hover,
.role-selector-trigger:focus-visible {
  border-color: var(--accent);
  background: var(--bg-hover);
  box-shadow: 0 0 0 3px var(--accent-dim);
  outline: none;
}
.role-selector-trigger:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.role-selector-chevron {
  color: var(--text-tertiary);
  font-size: 1.2rem;
  line-height: 1;
}
.role-picker-modal {
  max-width: 400px;
}
.role-picker-list {
  display: grid;
  gap: 6px;
}
.role-picker-option {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 52px;
  padding: 10px 12px;
  color: var(--text-primary);
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--transition), background var(--transition), color var(--transition);
}
.role-picker-option:hover,
.role-picker-option:focus-visible {
  border-color: var(--accent);
  background: var(--bg-hover);
  outline: none;
}
.role-picker-option.selected {
  border-color: var(--accent);
  background: var(--accent-dim);
  color: var(--accent);
}
.role-picker-option:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.role-picker-option-label {
  flex: 1;
  font-weight: 600;
}
.role-picker-option-desc {
  color: var(--text-tertiary);
  font-size: 0.78rem;
}
.role-picker-option-check {
  flex: 0 0 auto;
  color: var(--accent);
  font-weight: 700;
}

/* --- Folder selector --- */
.folder-selector-trigger {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  color: var(--text-primary);
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--transition), box-shadow var(--transition), background var(--transition);
}
.folder-selector-trigger:hover,
.folder-selector-trigger:focus-visible {
  border-color: var(--accent);
  background: var(--bg-hover);
  box-shadow: 0 0 0 3px var(--accent-dim);
  outline: none;
}
.folder-selector-icon,
.folder-selector-item-icon {
  flex: 0 0 auto;
}
.folder-selector-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.folder-selector-chevron {
  margin-left: auto;
  color: var(--text-tertiary);
  font-size: 1.3rem;
  line-height: 1;
}
.folder-selector-modal {
  max-width: 480px;
}
.folder-selector-list {
  max-height: min(58vh, 480px);
  overflow-y: auto;
  padding: 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-primary);
}
.folder-tree-node {
  display: block;
  width: 100%;
}
.folder-tree-children {
  margin-left: 24px;
  padding-left: 12px;
  border-left: 1px solid var(--border);
}
.folder-selector-item {
  display: flex;
  align-items: center;
  gap: 9px;
  min-height: 40px;
  width: 100%;
  padding: 7px 10px;
  color: var(--text-secondary);
  background: transparent;
  border: 0;
  border-radius: var(--radius);
  font: inherit;
  font-size: 0.9rem;
  text-align: left;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
.folder-selector-item:hover,
.folder-selector-item:focus-visible {
  background: var(--bg-hover);
  color: var(--text-primary);
  outline: none;
}
.folder-selector-item.selected {
  background: var(--accent-dim);
  color: var(--accent);
  font-weight: 600;
  outline: none;
}
.folder-tree-branch {
  flex: 0 0 16px;
  color: var(--text-tertiary);
  font-size: 1rem;
  line-height: 1;
  text-align: center;
}
.folder-selector-item-icon {
  flex: 0 0 auto;
  filter: saturate(0.85);
}
.folder-selector-item-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.folder-selector-check {
  margin-left: auto;
  color: var(--accent);
  font-weight: 700;
}

/* --- Login Agreement --- */
.agreement-modal {
  max-width: 560px;
}
.agreement-modal-intro {
  line-height: 1.7;
  margin-bottom: 18px;
}
.feedback-modal {
  max-width: 560px;
}
.feedback-modal-intro {
  line-height: 1.7;
  margin-bottom: 18px;
}
.feedback-modal .form-group:last-of-type {
  margin-bottom: 4px;
}
.agreement-links {
  display: grid;
  gap: 8px;
  margin-bottom: 20px;
}
.agreement-links a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  background: var(--bg-tertiary);
  transition: border-color 0.2s ease, background 0.2s ease;
}
.agreement-links a:hover,
.agreement-links a:focus-visible {
  border-color: var(--accent);
  background: var(--bg-hover);
}
.agreement-links a span {
  color: var(--text-tertiary);
}
.agreement-check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  color: var(--text-primary);
  font-size: 0.9rem;
  line-height: 1.6;
  cursor: pointer;
}
.agreement-check input {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  accent-color: var(--accent);
}

/* --- Download Confirmation --- */
.download-modal {
  max-width: 560px;
}

.download-file-meta {
  margin: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.download-file-meta-row {
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  border-bottom: 1px solid var(--border-subtle);
}

.download-file-meta-row:last-child {
  border-bottom: 0;
}

.download-file-meta dt,
.download-file-meta dd {
  margin: 0;
  padding: 12px 14px;
}

.download-file-meta dt {
  color: var(--text-secondary);
  background: var(--bg-tertiary);
  font-size: 0.82rem;
  font-weight: 500;
}

.download-file-meta dd {
  min-width: 0;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.download-file-hash {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
  font-size: 0.82rem;
  line-height: 1.65;
  word-break: break-all;
}

.download-modal-note,
.download-modal-status {
  margin: 16px 0 0;
  color: var(--text-secondary);
  font-size: 0.85rem;
  line-height: 1.7;
}

.download-modal-status {
  padding: 12px 14px;
  border-left: 3px solid var(--accent);
  background: var(--bg-tertiary);
  color: var(--text-primary);
}

.download-fallback-link {
  margin: 0 0.2em;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.download-fallback-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

.download-start-btn {
  min-width: 88px;
}

/* --- Standalone Download Page --- */
.download-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
}

.download-page-panel {
  width: min(100%, 520px);
  padding: 32px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--bg-secondary);
  box-shadow: var(--shadow-lg);
}

.download-page-state {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 18px;
  color: var(--text-secondary);
}

.download-page-spinner {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: btn-spin 0.7s linear infinite;
}

.download-page-error {
  margin-top: 18px;
  padding: 14px 16px;
  border-left: 3px solid var(--danger);
  background: var(--bg-tertiary);
  color: var(--text-primary);
  line-height: 1.65;
}

.download-page-actions {
  margin-top: 24px;
}

/* --- Toast --- */
.toast-container {
  position: fixed;
  top: 24px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 9999;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  pointer-events: none;
}

/* --- 上传进度条 --- */
.upload-progress {
  margin-top: 20px;
  padding: 16px;
  background: var(--bg-tertiary);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
}
.upload-progress-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
  gap: 12px;
}
.upload-progress-name {
  font-weight: 500;
  font-size: 0.9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
}
.upload-progress-stats {
  font-size: 0.85rem;
  color: var(--text-secondary);
  white-space: nowrap;
}
.upload-progress-bar-track {
  width: 100%;
  height: 6px;
  background: var(--border);
  border-radius: 3px;
  overflow: hidden;
  position: relative;
}
.upload-progress-bar-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--accent), #9588f7);
  border-radius: 3px;
  transition: width 0.3s ease, opacity 0.25s ease;
}
/* 未量化阶段（连存储服务器 / 注册 / 解压）：轨道上跑两道光带，一长一短接力扫过，
   已完成的填充同时压暗一点，既能看出在干活、又不丢已有进度。 */
.upload-progress-pending .upload-progress-bar-fill {
  opacity: 0.4;
}
.upload-progress-pending .upload-progress-bar-track::after,
.upload-progress-pending .upload-progress-bar-track::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: 3px;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.upload-progress-pending .upload-progress-bar-track::before {
  width: 200%;
  background: linear-gradient(90deg, transparent, rgba(150, 138, 255, 0.9), transparent);
  animation-name: track-sweep-long;
}
.upload-progress-pending .upload-progress-bar-track::after {
  width: 50%;
  background: linear-gradient(90deg, transparent, rgba(150, 138, 255, 0.9), transparent);
  animation-name: track-sweep-short;
  /* 不加 animation-delay：长短各自的"出场时段"已经写在 keyframes 里
     （长带占前半圈、短带占后半圈），两条同时启动才是接力扫动；
     再加半圈延迟会让两条同相位、同时可见（长带更宽，会把短带盖住） */
}
.upload-progress-speed {
  font-size: 0.8rem;
  color: var(--text-secondary);
  margin-top: 6px;
  text-align: right;
}

@keyframes track-sweep-long {
  0% { left: -200%; opacity: 1; }
  50% { left: 100%; opacity: 1; }
  50.01% { left: -200%; opacity: 0; }
  100% { left: -200%; opacity: 0; }
}
@keyframes track-sweep-short {
  0% { left: -50%; opacity: 0; }
  50% { left: -50%; opacity: 0; }
  50.01% { left: -50%; opacity: 1; }
  100% { left: 100%; opacity: 1; }
}

/* --- 操作进度条（批量移动 / 复制 / 删除） ---
   插在弹窗页脚上方，和上传进度条共用一套视觉语言，但更紧凑。 */
.op-progress {
  margin-top: 4px;
  padding: 12px 14px;
  background: var(--bg-tertiary);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  animation: op-progress-in 0.2s ease;
  transition: opacity 0.3s ease;
}
.op-progress-done {
  opacity: 0;
}
.op-progress-info {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 8px;
}
.op-progress-name {
  font-size: 0.85rem;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.op-progress-stats {
  font-size: 0.78rem;
  color: var(--text-secondary);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.op-progress-track {
  width: 100%;
  height: 6px;
  background: var(--border);
  border-radius: 3px;
  overflow: hidden;
  position: relative;
}
.op-progress-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--accent), #9588f7);
  border-radius: 3px;
  transition: width 0.25s ease;
}
/* 一项都还没完成时，轨道上留一道来回扫动的光带，说明请求正在跑。
   与遮罩、上传进度条共用同一套「长带 → 短带」接力扫动。 */
.op-progress-pending .op-progress-track::after,
.op-progress-pending .op-progress-track::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: 3px;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.op-progress-pending .op-progress-track::before {
  width: 200%;
  background: linear-gradient(90deg, transparent, rgba(150, 138, 255, 0.9), transparent);
  animation-name: track-sweep-long;
}
.op-progress-pending .op-progress-track::after {
  width: 50%;
  background: linear-gradient(90deg, transparent, rgba(150, 138, 255, 0.9), transparent);
  animation-name: track-sweep-short;
}
@keyframes op-progress-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

/* --- 页面切换过渡（登录页 → /app/） ---
   跳转是真实的多页导航，浏览器自己的加载指示没法由页面关掉；能做到的是在跳转前
   铺一整屏自己的遮罩：浏览器会一直显示旧页面最后画出的那一帧，直到新文档画出第一屏，
   于是整个等待过程里用户看到的是我们的扫动光带，而不是停在原地的登录表单。
   /app/ 首屏同样铺着这块遮罩（写在 app/index.html 里），数据就绪后再淡出。 */
.page-transition {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-primary);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
.page-transition.active {
  opacity: 1;
  pointer-events: auto;
}
/* 盖上来时不要淡入（等它淡完才跳转会让用户多看到一帧旧页面），收起时仍然淡出 */
.page-transition-instant {
  transition: none;
}
/* 首屏遮罩的排版令牌：深色沿用主题原生那一套，浅色覆盖成「白纸 + 墨蓝大字」 */
.page-transition {
  --pt-ink: var(--text-primary);
  --pt-quiet: var(--text-secondary);
  --pt-hairline: rgba(255, 255, 255, 0.16);
  --pt-rail: rgba(255, 255, 255, 0.08);
}
[data-theme="light"] .page-transition {
  --pt-ink: #201a54;
  --pt-quiet: #5c5a74;
  --pt-hairline: rgba(32, 26, 84, 0.16);
  --pt-rail: rgba(32, 26, 84, 0.1);
}

.page-transition-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  width: min(560px, 84vw);
  padding-bottom: 6vh; /* 视觉重心略高于正中，大字才不显得往下坠 */
  text-align: center;
}
/* 品牌行：图标 + Lucerna │ 页面名 —— 中间那道竖线是这套版式的记认 */
.pt-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.86rem;
  letter-spacing: 0.04em;
}
.pt-mark {
  width: 30px;
  height: 30px;
  object-fit: contain;
}
.pt-name {
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--pt-ink);
}
.pt-rule {
  width: 1px;
  height: 14px;
  background: var(--pt-hairline);
}
.pt-tag {
  color: var(--pt-quiet);
}
/* 一句大字：整块遮罩只有这一句要说的话，用负字距把两行咬紧 */
.pt-head {
  font-size: clamp(2.05rem, 7.2vw, 3.15rem);
  font-weight: 650;
  line-height: 1.12;
  letter-spacing: -0.02em;
  color: var(--pt-ink);
}
.pt-sub {
  max-width: 30ch;
  font-size: 0.92rem;
  line-height: 1.75;
  color: var(--pt-quiet);
}
/* 细线压在大字下面，像是这句话的下划线；两条光带在上面接力扫过 */
.page-transition-track {
  position: relative;
  width: 220px;
  height: 4px;
  border-radius: 999px;
  background: var(--pt-rail);
  overflow: hidden;
}
/* 两条光带接力扫过：一条很长（接近整条轨道长度，当作铺底的柔光）、
   一条中等长度（更亮更集中，作为高光）。靠 -0.5s 错开半圈，
   轨道上任何时刻只有一条在动：长带扫完接短带，短带扫完接长带。 */
.page-transition-track::before,
.page-transition-track::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: 999px;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.page-transition-track::before {
  width: 200%;
  background: linear-gradient(90deg, transparent, rgba(150, 138, 255, 0.9), transparent);
  animation-name: track-sweep-long;
}
.page-transition-track::after {
  width: 50%;
  background: linear-gradient(90deg, transparent, rgba(150, 138, 255, 0.9), transparent);
  animation-name: track-sweep-short;
}
/* 遮罩收起来之后就别再空跑动画了：/app/ 上它会一直留在 DOM 里 */
.page-transition:not(.active) .page-transition-track::before,
.page-transition:not(.active) .page-transition-track::after {
  animation: none;
}
.page-transition-text {
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--pt-quiet);
  opacity: 0.85;
}
/* 入场：品牌行 → 大字 → 说明 → 细线 → 状态，一行接一行轻轻抬起来。
   遮罩有时只停一两百毫秒，所以每行都短（0.5s）并带 fill-mode: both，
   任何一帧截下来都是完整的一屏，看不到半透明的一行字。 */
.pt-brand,
.pt-head,
.pt-sub,
.page-transition-track,
.page-transition-text {
  animation: pt-rise 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.pt-head { animation-delay: 0.05s; }
.pt-sub { animation-delay: 0.1s; }
.page-transition-track { animation-delay: 0.15s; }
.page-transition-text { animation-delay: 0.2s; }
@keyframes pt-rise {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .op-progress,
  .op-progress-fill,
  .op-progress-pending .op-progress-track::after,
  .upload-progress-pending .upload-progress-bar-track::after,
  .page-transition-track::before,
  .page-transition-track::after,
  .pt-brand,
  .pt-head,
  .pt-sub,
  .page-transition-text {
    animation: none;
    transition: none;
  }
}

/* --- Filter Bar (inline) --- */
.filter-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 12px;
  padding: 10px 16px;
  background: var(--bg-tertiary);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

.filter-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 6px;
}

.filter-label {
  font-size: 0.82rem;
  color: var(--text-secondary);
  font-weight: 600;
  white-space: nowrap;
  padding-top: 2px;
}

.filter-checkbox-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
}

.filter-checkbox-row label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.85rem;
  color: var(--text-primary);
  cursor: pointer;
  white-space: nowrap;
}

.filter-checkbox-row input[type="checkbox"] {
  appearance: none;
  width: 16px;
  height: 16px;
  margin: 0;
  flex: 0 0 16px;
  border: 1px solid var(--border-hover-strong);
  border-radius: 4px;
  background: var(--bg-primary);
  box-shadow: inset 0 0 0 2px var(--bg-primary);
  cursor: pointer;
  accent-color: var(--accent);
}

.filter-checkbox-row input[type="checkbox"]:checked {
  border-color: var(--accent);
  background: var(--accent);
  box-shadow: inset 0 0 0 2px var(--accent);
}

.filter-checkbox-row input[type="checkbox"]:checked::after,
.filter-checkall input[type="checkbox"]:checked::after,
.filter-menu-body input[type="checkbox"]:checked::after {
  content: "✓";
  display: block;
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  line-height: 14px;
  text-align: center;
}

.filter-item-count {
  font-size: 0.75rem;
  color: var(--text-tertiary);
}

.filter-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  margin-top: 10px;
}

.filter-info {
  font-size: 0.82rem;
  color: var(--text-secondary);
  white-space: nowrap;
}

.filter-dropdown {
  position: relative;
  display: inline-block;
}

.filter-text {
  display: inline;
  color: var(--text-secondary);
  font-size: 0.85rem;
  cursor: pointer;
  transition: color var(--transition);
  user-select: none;
}

.filter-text:hover {
  color: var(--accent-hover);
}

.filter-text.active {
  color: var(--accent);
}

.filter-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 200px;
  max-width: 280px;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  z-index: 500;
  display: none;
  overflow: hidden;
}

.filter-menu.open {
  display: block;
  animation: filterSlideIn 0.15s ease;
}

@keyframes filterSlideIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.filter-menu-header {
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
}

.filter-checkall {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  font-size: 0.82rem;
  color: var(--text-secondary);
}

.filter-checkall input[type="checkbox"] {
  accent-color: var(--accent);
}

.filter-menu-body {
  max-height: 220px;
  overflow-y: auto;
  padding: 4px 0;
}

.filter-menu-body label {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--text-primary);
  transition: background var(--transition);
}

.filter-menu-body label:hover {
  background: rgba(124,108,240,0.08);
}

.filter-menu-body input[type="checkbox"] {
  accent-color: var(--accent);
  flex-shrink: 0;
}

.filter-menu-body .filter-item-count {
  margin-left: auto;
  font-size: 0.75rem;
  color: var(--text-tertiary);
}

.filter-menu-footer {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
  padding: 8px 12px;
  border-top: 1px solid var(--border);
}

.filter-clear {
  color: var(--danger);
  font-size: 0.82rem;
}

.filter-clear:hover {
  background: rgba(248,113,113,0.1);
}

#toggleFilterBtn.active {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}


/* Backdrop for filter dropdowns */
.filter-backdrop {
  position: fixed;
  inset: 0;
  z-index: 499;
  display: none;
}

.filter-backdrop.active {
  display: block;
}

.toast {
  background: rgba(30, 30, 56, 0.95);
  border: 1px solid var(--highlight-ring);
  border-radius: 50px;
  padding: 12px 24px;
  font-size: 0.88rem;
  color: var(--text-primary);
  box-shadow: 0 0 20px rgba(124, 108, 240, 0.15), 0 4px 16px rgba(0,0,0,0.5);
  display: block;
  min-width: 200px;
  max-width: 360px;
  overflow-wrap: break-word;
  text-align: center;
  animation: toastIn 0.4s linear;
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  pointer-events: auto;
}
.toast.toast-out {
  animation: toastOut 0.25s linear forwards;
}
.toast-success { border-bottom: 3px solid var(--success); }
.toast-error   { border-bottom: 3px solid var(--danger); }
.toast-info    { border-bottom: 3px solid var(--info); }
.toast-warning { border-bottom: 3px solid var(--warning); }

@keyframes toastIn {
  from { opacity: 0; transform: translateY(-24px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes toastOut {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(-20px); }
}

/* --- Empty State --- */
.empty-state {
  text-align: center;
  padding: 60px 20px;
  color: var(--text-tertiary);
}
.empty-state .empty-icon {
  font-size: 3rem;
  margin-bottom: 16px;
  opacity: 0.5;
}
.empty-state h3 {
  color: var(--text-secondary);
  margin-bottom: 6px;
}
.empty-state p {
  font-size: 0.88rem;
  max-width: 320px;
  margin: 0 auto;
}

/* --- 胶囊开关（设置弹窗里的开 / 关） --- */
.switch {
  position: relative;
  flex: 0 0 auto;
  width: 46px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-elevated);
  cursor: pointer;
  transition: background 0.24s ease, border-color 0.24s ease;
  -webkit-tap-highlight-color: transparent;
}
.switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  transition: transform 0.24s cubic-bezier(0.22, 1, 0.36, 1);
}
.switch[aria-checked="true"] {
  background: var(--accent);
  border-color: var(--accent);
}
.switch[aria-checked="true"] .switch-knob {
  transform: translateX(20px);
}

/* --- 设置弹窗 --- */
.settings-modal {
  max-width: 680px; /* 与邮箱窗口同宽 */
  padding: 24px;
}
.settings-list {
  display: flex;
  flex-direction: column;
}
.settings-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 14px 0;
}
.settings-row + .settings-row {
  border-top: 1px solid var(--border-subtle);
}
.settings-row-text {
  flex: 1;
  min-width: 0;
}
.settings-row-title {
  font-size: 0.92rem;
  font-weight: 500;
}
.settings-row-hint {
  margin-top: 3px;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--text-tertiary);
}

/* 主题的三段式选择（跟随系统 / 浅色 / 深色） */
.segmented {
  position: relative; /* 滑动指示块的定位基准 */
  display: inline-flex;
  flex: 0 0 auto;
  gap: 2px;
  padding: 3px;
  background: var(--bg-tertiary);
  border: 1px solid var(--border);
  border-radius: 999px;
}
/* 选中态交给一个绝对定位的指示块滑过去，切换时不再瞬闪（位置与宽度由 JS 量出来） */
.segmented-indicator {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 0;
  width: 0;
  border-radius: 999px;
  background: var(--accent);
  pointer-events: none;
  transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), width 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}
.seg-btn {
  position: relative;
  z-index: 1;
  padding: 5px 12px;
  border: 0;
  border-radius: 999px;
  background: none;
  font-family: inherit;
  font-size: 0.8rem;
  color: var(--text-secondary);
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--transition);
}
.seg-btn:hover {
  color: var(--text-primary);
}
.seg-btn[aria-checked="true"] {
  color: #fff;
}

/* --- Loading --- */
.spinner {
  width: 20px;
  height: 20px;
  border: 2px solid var(--bg-elevated);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* --- 文件列表区的换页过渡（进下级从右滑入、返回上级从左滑入） ---
   旧内容挪进"幽灵层"朝一侧滑出、新内容同时从另一侧滑入，中间不会闪一下空白 */
#fileListContainer {
  position: relative;
}

.drive-ghost {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1;
  pointer-events: none;
}

.drive-swap-in-fwd  { animation: driveInFwd  0.26s cubic-bezier(0.22, 1, 0.36, 1) both; }
.drive-swap-in-back { animation: driveInBack 0.26s cubic-bezier(0.22, 1, 0.36, 1) both; }
.drive-ghost-fwd    { animation: driveOutFwd  0.22s ease both; }
.drive-ghost-back   { animation: driveOutBack 0.22s ease both; }

@keyframes driveInFwd   { from { opacity: 0; transform: translateX(28px); }  to { opacity: 1; transform: none; } }
@keyframes driveOutFwd  { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateX(-28px); } }
@keyframes driveInBack  { from { opacity: 0; transform: translateX(-28px); } to { opacity: 1; transform: none; } }
@keyframes driveOutBack { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateX(28px); } }

.loading-screen {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 300px;
}
.loading-screen .spinner {
  width: 28px;
  height: 28px;
}

/* --- 管理后台身份验证占位（验证通过前不显示任何后台信息） --- */
.admin-auth-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  min-height: 100vh;
}
.admin-auth-loading .spinner {
  width: 32px;
  height: 32px;
}
.admin-auth-loading p {
  margin: 0;
  font-size: 14px;
  color: var(--text-tertiary);
}

/* --- Checkbox list (for upload class selection) --- */
.checkbox-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 200px;
  overflow-y: auto;
  padding: 4px 0;
}

.checkbox-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border-radius: var(--radius);
  cursor: pointer;
  transition: background var(--transition);
}
.checkbox-item:hover { background: var(--bg-tertiary); }

.checkbox-item input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  cursor: pointer;
}

/* --- Drive layout --- */
.drive-layout {
  display: flex;
  height: calc(100vh - 52px);
}

.drive-sidebar {
  width: 240px;
  background: var(--bg-secondary);
  border-right: 1px solid var(--border);
  padding: 16px 0;
  flex-shrink: 0;
  overflow-y: auto;
}

.drive-sidebar-title {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 0 18px;
  margin-bottom: 8px;
}

.drive-sidebar-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 18px;
  font-size: 0.88rem;
  color: var(--text-secondary);
  cursor: pointer;
  transition: all var(--transition);
  border: none;
  background: none;
  width: 100%;
  text-align: left;
  font-family: var(--font);
}
.drive-sidebar-item:hover {
  background: var(--bg-tertiary);
  color: var(--text-primary);
}
.drive-sidebar-item.active {
  background: var(--accent-dim);
  color: var(--accent);
  border-right: 2px solid var(--accent);
}

.drive-main {
  flex: 1;
  overflow-y: auto;
  padding: 24px;
}

.drive-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 20px;
}
.drive-toolbar h2 { flex: 1; }

/* --- Search Bar --- */
.drive-search-bar {
  margin-bottom: 16px;
}

.search-input-wrap {
  position: relative;
  display: flex;
  align-items: center;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: border-color var(--transition);
}

.search-input-wrap:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-dim);
}

.search-icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

.search-input {
  flex: 1;
  background: transparent;
  border: none;
  color: var(--text-primary);
  font-size: 0.9rem;
  padding: 10px 40px 10px 38px;
  outline: none;
  font-family: var(--font);
}

.search-input::placeholder {
  color: var(--text-tertiary);
}

/* 隐藏浏览器自带的搜索清除按钮（×），避免与自定义 clear 按钮重复显示两个 × */
.search-input::-webkit-search-cancel-button,
.search-input::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
  display: none;
}

.search-input::-ms-clear {
  display: none;
}

.search-clear {
  position: absolute;
  right: 4px;
  padding: 4px 8px;
  font-size: 0.85rem;
}

/* --- Search Results --- */
.search-results {
  margin-bottom: 16px;
}

.search-results-header {
  color: var(--text-secondary);
  font-size: 0.85rem;
  margin-bottom: 10px;
  padding: 0 4px;
}

.search-result-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 6px;
  transition: background var(--transition);
}

.search-result-item:hover {
  background: var(--bg-tertiary);
}

.search-result-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.search-result-info {
  flex: 1;
  min-width: 0;
}

.search-result-name {
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.search-result-path {
  font-size: 0.8rem;
  color: var(--text-tertiary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.search-result-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

.search-no-results {
  text-align: center;
  padding: 32px;
  color: var(--text-tertiary);
}

/* --- File Highlight --- */
.file-item.file-highlight {
  background: rgba(251, 191, 36, 0.12);
  border: 1px solid rgba(251, 191, 36, 0.4);
  border-radius: var(--radius);
  animation: highlightPulse 2s ease-in-out;
  padding: 8px 12px;
  margin: 2px 0;
}

@keyframes highlightPulse {
  0% { box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.4); }
  50% { box-shadow: 0 0 0 8px rgba(251, 191, 36, 0); }
  100% { box-shadow: 0 0 0 0 rgba(251, 191, 36, 0); }
}

/* --- Admin layout --- */
.admin-layout {
  display: flex;
  height: calc(100vh - 52px);
}

.admin-sidebar {
  width: 220px;
  background: var(--bg-secondary);
  border-right: 1px solid var(--border);
  padding: 16px 0;
  flex-shrink: 0;
}

.admin-main {
  flex: 1;
  overflow-y: auto;
  padding: 24px;
}

/* 后台欢迎页：还没从左侧选择分组时，欢迎语与提示整体在详情区居中 */
.admin-welcome {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  /* 撑满详情区可视高度：视口高 - 顶栏 52px - .admin-main 的上下内边距 48px。
     高度优先用 app.js 实测的 --app-vh：手机地址栏会收放，100vh 是「地址栏收起时」的高度，
     直接拿它算居中的话内容会整体偏下；没测到时依次退回 dvh / vh。 */
  min-height: calc(100vh - 100px);
  min-height: calc(100dvh - 100px);
  min-height: calc(var(--app-vh, 100dvh) - 100px);
  text-align: center;
}

/* --- Utility --- */
.flex { display: flex; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-4 { gap: 4px; }
.gap-8 { gap: 8px; }
.gap-12 { gap: 12px; }
.gap-16 { gap: 16px; }
.gap-24 { gap: 24px; }
.mt-8 { margin-top: 8px; }
.mt-12 { margin-top: 12px; }
.mt-16 { margin-top: 16px; }
.mt-20 { margin-top: 20px; }
.mt-24 { margin-top: 24px; }
.mb-8 { margin-bottom: 8px; }
.mb-12 { margin-bottom: 12px; }
.mb-16 { margin-bottom: 16px; }
.mb-20 { margin-bottom: 20px; }
.mb-24 { margin-bottom: 24px; }
.w-full { width: 100%; }
.hidden { display: none !important; }
.truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* --- Divider --- */
.divider {
  height: 1px;
  background: var(--border);
  margin: 16px 0;
}

/* --- Link button --- */
.link-btn {
  background: none;
  border: none;
  color: var(--accent);
  cursor: pointer;
  font-size: 0.88rem;
  font-family: var(--font);
  padding: 0;
  transition: color var(--transition);
}
.link-btn:hover { color: var(--accent-hover); }

/* --- Responsive --- */
/* ============================================
   移动端全面适配 (≤768px)
   桌面端原有样式完全不动，仅在此处覆盖
   ============================================ */
.batch-selectable-item {
  cursor: pointer;
}

@media (max-width: 768px) {
  .batch-action-bar {
    align-items: stretch;
    margin-top: -4px;
  }
  .batch-action-bar .batch-selection-count,
  .batch-action-bar .batch-delete-hint {
    flex-basis: 100%;
  }
  .batch-action-bar .btn {
    flex: 1 1 auto;
  }
  .file-item .batch-file-checkbox {
    flex-basis: 20px;
    width: 20px;
  }
  .file-item.batch-selectable-item,
  .folder-item.batch-selectable-item {
    grid-template-columns: 36px minmax(0, 1fr) 24px;
  }
  .batch-selectable-item .batch-file-checkbox {
    grid-column: 3;
    grid-row: 1;
    justify-self: end;
  }

  /* --- 基础排版 --- */
  html {
    font-size: 14px;
  }
  h1 { font-size: 1.4rem; }
  h2 { font-size: 1.15rem; }
  h3 { font-size: 1rem; }

  .container {
    padding: 0 12px;
  }

  /* --- Topbar --- */
  .topbar {
    height: 48px;
    padding: 0 12px;
    gap: 8px;
  }
  /* 手机端保留品牌名：图标右侧不再留空 */
  .topbar-brand {
    gap: 8px;
    flex-shrink: 0;
  }
  .topbar-brand span {
    display: block;
    font-size: 0.9rem;
    white-space: nowrap;
  }
  .topbar-brand-icon {
    width: 32px;
    height: 32px;
    font-size: 0.85rem;
    border-radius: 6px;
  }
  .topbar-user {
    gap: 4px;
    font-size: 0.78rem;
    flex-wrap: wrap;
    justify-content: flex-end;
  }
  .topbar-user .btn-ghost {
    padding: 0 8px;
    font-size: 0.75rem;
  }
  /* 手机上整体矮一点，四个按钮仍然等高 */
  .topbar .btn {
    height: 30px;
  }
  .topbar-avatar {
    width: 22px;
    height: 22px;
    font-size: 0.68rem;
    line-height: 20px;
  }
  .user-menu-trigger {
    padding: 0 8px;
    gap: 5px;
  }
  .user-menu-trigger span:first-child {
    max-width: 60px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* --- Auth --- */
  .page-centered {
    padding: 16px;
  }
  .auth-card {
    padding: 24px 16px;
    width: 100%;
    max-width: 400px;
  }
  .auth-header h1 {
    font-size: 1.3rem;
  }
  .auth-header p {
    font-size: 0.82rem;
  }
  .auth-logo {
    font-size: 2rem;
  }

  /* --- Class Selection --- */
  .class-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 8px;
  }
  .class-card {
    padding: 18px 12px;
  }
  .class-card .class-icon {
    width: 40px;
    height: 40px;
    font-size: 1.1rem;
  }
  .class-card .class-name {
    font-size: 0.9rem;
  }

  /* 手机端隐藏管理后台入口 */
  #adminEntryBtn {
    display: none !important;
  }

  /* --- Drive Layout --- */
  .drive-layout {
    flex-direction: column;
  }
  /* 移动端侧边栏：从左淡入 / 淡出。
     用 visibility + opacity + transform 代替 display 切换，否则过渡不会生效 */
  .drive-sidebar {
    position: fixed;
    top: 48px;
    left: 0;
    width: 260px;
    height: calc(100vh - 48px);
    z-index: 200;
    box-shadow: var(--shadow-lg);
    border-right: 1px solid var(--border);
    visibility: hidden;
    opacity: 0;
    transform: translateX(-40px);
    pointer-events: none;
    transition:
      opacity 0.24s ease,
      transform 0.24s cubic-bezier(0.4, 0, 1, 1),
      visibility 0s linear 0.24s;
  }
  .drive-sidebar.mobile-open {
    visibility: visible;
    opacity: 1;
    transform: translateX(0);
    pointer-events: auto;
    transition:
      opacity 0.32s ease,
      transform 0.32s cubic-bezier(0.22, 1, 0.36, 1),
      visibility 0s;
  }
  /* 移动端侧边栏遮罩：跟着一起淡入淡出 */
  .sidebar-backdrop {
    position: fixed;
    inset: 0;
    top: 48px;
    background: rgba(0,0,0,0.5);
    z-index: 199;
    visibility: hidden;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.24s ease, visibility 0s linear 0.24s;
  }
  .sidebar-backdrop.active {
    visibility: visible;
    opacity: 1;
    pointer-events: auto;
    transition: opacity 0.32s ease, visibility 0s;
  }

  .drive-main {
    padding: 12px;
    height: auto;
    flex: 1;
  }

  /* Drive Toolbar */
  .drive-toolbar {
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 12px;
  }
  .drive-toolbar h2 {
    flex: 1 1 100%;
    font-size: 1.1rem;
    margin-bottom: 4px;
  }
  .drive-toolbar .btn {
    font-size: 0.78rem;
    padding: 6px 12px;
  }
  .drive-toolbar .btn-sm {
    padding: 5px 10px;
    font-size: 0.75rem;
  }

  /* 面包屑 */
  .breadcrumb {
    padding: 8px 12px;
    font-size: 0.78rem;
    overflow-x: auto;
    white-space: nowrap;
    -webkit-overflow-scrolling: touch;
  }
  .breadcrumb::-webkit-scrollbar {
    display: none;
  }

  /* --- File List — 移动端多行卡片式 --- */
  .file-item {
    display: grid;
    grid-template-columns: 36px 1fr;
    gap: 4px 10px;
    align-items: center;
    padding: 12px;
    background: var(--bg-secondary);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    margin-bottom: 6px;
  }
  .file-item:hover {
    background: var(--bg-tertiary);
  }

  /* display:contents 让 .file-info 的子元素直接参与 grid */
  .file-item > .file-info {
    display: contents;
  }

  .file-item .file-icon {
    grid-column: 1;
    grid-row: 1;
    width: 36px;
    height: 36px;
    font-size: 1rem;
  }
  .file-item .file-name {
    grid-column: 2;
    grid-row: 1;
    font-size: 0.88rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .file-item .file-meta {
    grid-column: 1 / -1;
    font-size: 0.72rem;
    gap: 8px;
    flex-wrap: wrap;
  }
  .file-item .file-actions {
    grid-column: 1 / -1;
    gap: 6px;
    flex-wrap: wrap;
    padding-top: 2px;
  }
  .file-item .file-actions .btn {
    font-size: 0.75rem;
    padding: 4px 10px;
  }
  .file-item .file-actions .btn-icon {
    width: 28px;
    height: 28px;
    font-size: 0.78rem;
    padding: 0;
  }

  /* --- Admin Layout --- */
  .admin-layout {
    flex-direction: column;
  }
  .admin-sidebar {
    display: flex;
    flex-direction: row;
    width: 100%;
    height: auto;
    padding: 0;
    overflow-x: auto;
    border-right: none;
    border-bottom: 1px solid var(--border);
    -webkit-overflow-scrolling: touch;
  }
  .admin-sidebar::-webkit-scrollbar {
    display: none;
  }
  .admin-sidebar .drive-sidebar-item {
    white-space: nowrap;
    flex-shrink: 0;
    padding: 10px 14px;
    font-size: 0.82rem;
    border-right: none;
    border-bottom: 2px solid transparent;
  }
  .admin-sidebar .drive-sidebar-item.active {
    border-right: none;
    border-bottom: 2px solid var(--accent);
  }
  .admin-main {
    padding: 12px;
    height: auto;
  }
  /* 窄屏时侧边栏变成顶部的横向标签条（约 40px），可用的竖向空间相应减去它的高度 */
  .admin-welcome {
    min-height: calc(100vh - 116px);
    min-height: calc(100dvh - 116px);
    min-height: calc(var(--app-vh, 100dvh) - 116px);
  }

  /* Admin tables → 卡片式布局 */
  .table-wrap {
    border: none;
    border-radius: 0;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .table {
    font-size: 0.78rem;
  }
  .table th {
    padding: 8px 10px;
    font-size: 0.72rem;
  }
  .table td {
    padding: 8px 10px;
  }

  /* --- Tabs --- */
  .tabs {
    gap: 0;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .tabs::-webkit-scrollbar {
    display: none;
  }
  .tab {
    padding: 8px 14px;
    font-size: 0.82rem;
    white-space: nowrap;
  }

  /* --- Modals --- */
  .modal-overlay {
    align-items: flex-end; /* 底部弹出 */
  }
  .modal {
    width: 100%;
    max-width: 100%;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    padding: 20px 16px;
    max-height: 90vh;
    transform: translateY(100%);
  }
  .modal-overlay.active .modal {
    transform: translateY(0);
  }
  .modal-header h3 {
    font-size: 1rem;
  }
  .modal-footer {
    flex-direction: column;
    gap: 8px;
  }
  .modal-footer .btn {
    width: 100%;
  }
  .download-file-meta-row {
    grid-template-columns: 76px minmax(0, 1fr);
  }
  .download-file-meta dt,
  .download-file-meta dd {
    padding: 10px 12px;
  }
  .download-page {
    padding: 16px;
  }
  .download-page-panel {
    padding: 24px 20px;
  }

  /* --- Form --- */
  .form-row {
    flex-direction: column;
    gap: 0;
  }
  .form-input, .form-select {
    padding: 8px 12px;
    font-size: 0.85rem;
  }
  .form-label {
    font-size: 0.8rem;
  }

  /* --- Filter Bar --- */
  .filter-bar {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
  }
  .filter-actions {
    width: 100%;
    flex-wrap: wrap;
  }
  .filter-info {
    width: 100%;
    text-align: left;
    font-size: 0.78rem;
  }
  .filter-menu {
    left: 0;
    min-width: 180px;
  }

  /* --- Buttons --- */
  .btn {
    padding: 7px 14px;
    font-size: 0.85rem;
  }
  .btn-lg {
    padding: 10px 20px;
    font-size: 0.92rem;
  }

  /* --- Toast --- */
  .toast {
    min-width: auto;
    max-width: calc(100vw - 40px);
    font-size: 0.82rem;
    padding: 10px 18px;
    border-radius: 40px;
    display: block;
    overflow-wrap: break-word;
  }
  .toast-container {
    top: 12px;
    left: 50%;
    transform: translateX(-50%);
  }

  /* --- Upload --- */
  .upload-dropzone {
    padding: 14px;
    font-size: 0.82rem;
  }
  .upload-plus {
    width: 28px;
    height: 28px;
    font-size: 18px;
  }
  .upload-hint {
    font-size: 13px;
  }

  .upload-file-item {
    font-size: 13px;
    padding: 5px 8px;
  }

  /* --- Empty State --- */
  .empty-state {
    padding: 32px 16px;
  }
  .empty-state .empty-icon {
    font-size: 2.5rem;
  }

  /* --- Utility overrides --- */
  .mb-24 { margin-bottom: 16px; }
  .mb-20 { margin-bottom: 14px; }
  .mb-16 { margin-bottom: 12px; }

  /* --- flex items-center justify-between → stack on mobile --- */
  .container > .flex.items-center.justify-between {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }
  .container > .flex.items-center.justify-between .btn {
    align-self: flex-start;
  }

  /* --- Folder tree sidebar --- */
  .folder-tree-item {
    padding: 6px 14px;
    font-size: 0.82rem;
  }
  .drive-sidebar-title {
    padding: 0 14px;
    font-size: 0.7rem;
  }

  /* --- Separator --- */
  .separator {
    margin: 14px 0;
    font-size: 0.75rem;
    gap: 8px;
  }

  /* --- Override inline max-width on mobile --- */
  #adminFileSearch {
    max-width: 100% !important;
  }
}

/* ============================================
   小屏手机深度适配 (≤480px)
   ============================================ */
@media (max-width: 480px) {
  html {
    font-size: 13px;
  }

  h1 { font-size: 1.25rem; }
  h2 { font-size: 1.05rem; }

  /* Topbar 进一步精简 */
  .topbar {
    padding: 0 8px;
    gap: 4px;
  }
  .topbar-user .btn-ghost {
    padding: 0 6px;
    font-size: 0.78rem;
  }
  .topbar-user .user-menu-trigger span:first-child {
    max-width: 40px;
  }

  /* Class grid → 2列紧凑 */
  .class-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 6px;
  }
  .class-card {
    padding: 14px 8px;
  }
  .class-card .class-icon {
    width: 36px;
    height: 36px;
    font-size: 1rem;
    margin-bottom: 8px;
  }
  .class-card .class-name {
    font-size: 0.88rem;
  }

  /* File list 更紧凑 */
  .file-item, .folder-item {
    padding: 10px;
    grid-template-columns: 32px 1fr;
    gap: 3px 8px;
  }
  .file-item .file-icon, .folder-item .file-icon {
    width: 32px;
    height: 32px;
    font-size: 0.9rem;
  }
  .file-item .file-name, .folder-item .file-name {
    font-size: 0.86rem;
  }
  .file-item .file-meta, .folder-item .file-meta {
    font-size: 0.74rem;
  }
  .file-item .file-actions .btn, .folder-item .file-actions .btn {
    font-size: 0.76rem;
    padding: 3px 8px;
  }
  .file-actions .btn-icon {
    width: 28px;
    height: 28px;
    font-size: 0.75rem;
  }

  /* Admin sidebar tabs */
  .admin-sidebar .drive-sidebar-item {
    padding: 8px 10px;
    font-size: 0.82rem;
  }

  /* Modal 满屏 */
  .modal {
    border-radius: 0;
    padding: 16px 12px;
    max-height: 100vh;
  }

  /* Drive toolbar */
  .drive-toolbar .btn {
    font-size: 0.8rem;
    padding: 5px 10px;
  }
}

/* --- Animations --- */
@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes slideUp {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

.animate-fade { animation: fadeIn 0.3s ease; }
.animate-slide { animation: slideUp 0.35s ease; }

/* --- File type icon colors --- */
.file-icon-pdf  { color: #f87171; }
.file-icon-doc  { color: #60a5fa; }
.file-icon-xls  { color: #4ade80; }
.file-icon-ppt  { color: #fbbf24; }
.file-icon-img  { color: #c084fc; }
.file-icon-zip  { color: #fb923c; }
.file-icon-other { color: var(--text-tertiary); }

/* --- Separator with text --- */
.separator {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 20px 0;
  color: var(--text-tertiary);
  font-size: 0.82rem;
}
.separator::before,
.separator::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* ============================================
   面包屑导航
   ============================================ */
.breadcrumb {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 10px 20px;
  font-size: 0.88rem;
  color: var(--text-secondary);
  border-bottom: 1px solid var(--border-subtle);
  background: var(--bg-secondary);
}

.breadcrumb-item {
  cursor: pointer;
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  transition: color var(--transition), background var(--transition);
}

.breadcrumb-item:hover {
  color: var(--accent-hover);
  background: var(--bg-tertiary);
}

.breadcrumb-separator {
  color: var(--text-tertiary);
  padding: 0 2px;
  font-size: 1rem;
}

/* ============================================
   文件夹项
   ============================================ */
.folder-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border-subtle);
  transition: background var(--transition);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* 鼠标点击不显示焦点环；键盘 Tab 走到条目上仍然给一圈可见的焦点环（见下） */
.folder-item:focus:not(:focus-visible),
.folder-item:focus-within:not(:focus-visible) {
  outline: none;
}

/* 条目是 div 但补了 role="button" tabindex="0"，键盘走到这里时要有明确的焦点提示 */
.file-item:focus-visible,
.folder-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px; /* 贴着条目内缘画，避免和相邻条目的描边叠在一起 */
}

.folder-item:hover {
  background: var(--bg-hover);
}

.folder-item .file-icon {
  font-size: 1.3rem;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-dim);
  border-radius: var(--radius);
}

.folder-item .file-info {
  flex: 1;
  min-width: 0;
}

.folder-item .file-name {
  font-weight: 500;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.folder-item .file-meta {
  font-size: 0.78rem;
  color: var(--text-tertiary);
  margin-top: 2px;
}

.folder-item .file-actions {
  flex-shrink: 0;
  display: flex;
  gap: 6px;
}

/* ============================================
   管理员搜索框
   ============================================ */
#adminFileSearch {
  padding: 8px 14px;
  font-size: 0.9rem;
}

#adminFileSearch:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-glow);
}

/* ============================================
   编辑文件模态窗
   ============================================ */
#editFileModal .checkbox-list {
  max-height: 200px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px;
}

#editFileModal .checkbox-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  transition: background var(--transition);
}

#editFileModal .checkbox-item:hover {
  background: var(--bg-hover);
}

/* --- Upload dropzone & file list --- */
.hidden-input {
  display: none;
}

.upload-dropzone {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px;
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color var(--transition), background var(--transition);
}

.upload-dropzone:hover {
  border-color: var(--accent);
  background: var(--bg-hover);
}

/* 拖拽文件悬停时的视觉反馈 */
.upload-dropzone.dragover {
  border-color: var(--accent);
  background: var(--accent-dim);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.upload-file-list.dragover {
  outline: 2px dashed var(--accent);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
}

.upload-plus {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-size: 20px;
  font-weight: 700;
  line-height: 1;
  flex-shrink: 0;
}

.upload-hint {
  color: var(--text-secondary);
  font-size: 13px;
}

.upload-file-list {
  margin-top: 8px;
  max-height: 180px;
  overflow-y: auto;
}

.upload-file-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  background: var(--bg-hover);
  margin-bottom: 4px;
  font-size: 13px;
}

.upload-file-item-name {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-right: 8px;
}

.upload-file-item-size {
  color: var(--text-secondary);
  font-size: 12px;
  margin-right: 8px;
  flex-shrink: 0;
}

.upload-file-item-remove {
  cursor: pointer;
  color: var(--text-secondary);
  font-size: 16px;
  line-height: 1;
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  border: none;
  background: transparent;
  transition: color var(--transition), background var(--transition);
  flex-shrink: 0;
}

.upload-file-item-remove:hover {
  color: var(--danger);
  background: rgba(220, 38, 38, 0.08);
}

/* 压缩包条目上的「解压」勾选（上传窗口内） */
.upload-file-item-extract {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 7px;
  margin-right: 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-secondary);
  font-size: 11px;
  line-height: 1.6;
  cursor: pointer;
  flex-shrink: 0;
  user-select: none;
  transition: color var(--transition), border-color var(--transition), background var(--transition);
}

.upload-file-item-extract:hover {
  color: var(--text-primary);
  border-color: var(--accent);
}

.upload-file-item-extract input {
  width: 12px;
  height: 12px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.upload-file-item-extract.is-on {
  color: var(--accent);
  border-color: var(--accent);
  background: rgba(124, 108, 240, 0.14);
}

/* 关闭态必须一眼看出来：否则用户点一下想把开关"勾上"，反而把它关了 */
.upload-file-item-extract.is-off {
  color: var(--text-tertiary);
  border-style: dashed;
  background: transparent;
}

.upload-extract-note.is-off {
  color: var(--warning, #d97706);
}

.upload-extract-note {
  margin: 6px 2px 0;
  color: var(--text-tertiary);
  font-size: 11px;
  line-height: 1.6;
}

#uploadModal.upload-locked .upload-file-item-extract {
  opacity: 0.5;
  pointer-events: none;
}

/* 上传进行中：锁定文件列表，隐藏“添加文件”加号框与条目右侧的删除按钮 */
#uploadModal.upload-locked .upload-dropzone {
  display: none;
}
#uploadModal.upload-locked .upload-file-item-remove {
  display: none;
}

/* --- Upload warning text --- */
/* 标题与警告的间距、警告与“选择文件”的间距取中间值，视觉均衡 */
#uploadModal .modal-header {
  margin-bottom: 8px;
}
.upload-warning-text {
  color: #ef4444;
  font-weight: 700;
  font-size: 13px;
  text-align: center;
  padding: 4px 16px 12px;
  cursor: pointer;
  user-select: none;
  transition: opacity 0.15s;
}

.upload-warning-text:hover {
  opacity: 0.8;
}

/* --- Upload network status（当前走 IPv4 还是 IPv6） --- */
/* 位置在按钮上方：上传前的最后一屏顺带说明"这条线路好不好"。
   只有 IPv4 需要用户动手，所以只让那一种状态亮出颜色，其余保持安静。 */
.upload-net-status {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 0 0 2px; /* 与 .modal-footer 的 24px 上边距一起构成 26px 间距 */
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-primary);
}

.upload-net-status[hidden] {
  display: none;
}

/* 颜色只出现在图标底片上，整块卡片不染色，免得把按钮的视觉重心抢走 */
.upload-net-status-icon {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--bg-tertiary);
  color: var(--text-tertiary);
}

.upload-net-status-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  padding-top: 1px;
}

.upload-net-status-title {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.4;
  color: var(--text-primary);
}

.upload-net-status-desc {
  font-size: 12px;
  line-height: 1.65;
  color: var(--text-secondary);
}

/* IPv6：一切正常，安静地报个好消息 */
.upload-net-status.is-ipv6 {
  border-color: rgba(74, 222, 128, 0.22);
  background: rgba(74, 222, 128, 0.06);
}

.upload-net-status.is-ipv6 .upload-net-status-icon {
  background: rgba(74, 222, 128, 0.14);
  color: var(--success);
}

/* IPv4：需要用户动手，颜色提一档 */
.upload-net-status.is-ipv4 {
  border-color: rgba(251, 191, 36, 0.3);
  background: rgba(251, 191, 36, 0.07);
}

.upload-net-status.is-ipv4 .upload-net-status-icon {
  background: rgba(251, 191, 36, 0.16);
  color: var(--warning);
}

/* 手动切换入口：做成文字链接，别和主按钮抢注意力 */
.upload-net-status-switch {
  align-self: flex-start;
  margin-top: 4px;
  padding: 0;
  border: 0;
  background: none;
  font-family: var(--font);
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.upload-net-status-switch[hidden] {
  display: none;
}

.upload-net-status-switch:hover {
  color: var(--accent-hover);
}

.upload-net-status-switch:disabled {
  color: var(--text-tertiary);
  text-decoration: none;
  cursor: default;
}

/* --- Warning modal --- */
.warning-modal {
  max-width: 480px;
  text-align: center;
}

.warning-modal-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 20px 24px 0;
}

.warning-modal-header .warning-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--danger);
}

.warning-modal-header h3 {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--danger);
  margin: 0;
}

.warning-modal-body {
  padding: 16px 24px 4px;
  line-height: 1.7;
  font-size: 14px;
  color: var(--text-primary);
  text-align: left;
}

.warning-modal-footer {
  padding: 16px 24px 24px;
}

.warning-modal-footer .btn {
  min-width: 140px;
}

/* --- Folder tree sidebar --- */
.folder-tree {
  display: flex;
  flex-direction: column;
}

.folder-tree-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 16px;
  font-size: 0.86rem;
  color: var(--text-secondary);
  cursor: pointer;
  transition: all var(--transition);
  border: none;
  background: none;
  width: 100%;
  text-align: left;
  font-family: var(--font);
  border-radius: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.folder-tree-item:hover {
  background: var(--bg-tertiary);
  color: var(--text-primary);
}

.folder-tree-item.active {
  background: var(--accent-dim);
  color: var(--accent);
  border-right: 2px solid var(--accent);
  font-weight: 500;
}

.folder-tree-root {
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--text-primary);
}

.folder-tree-divider {
  height: 1px;
  background: var(--border);
  margin: 4px 12px;
}

/* 目录树展开/折叠箭头 */
.folder-tree-arrow {
  flex: 0 0 14px;
  width: 14px;
  height: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.58rem;
  line-height: 1;
  color: var(--text-tertiary);
  cursor: pointer;
  user-select: none;
  border-radius: 3px;
  transition: all var(--transition);
}

.folder-tree-arrow:hover {
  background: var(--bg-tertiary);
  color: var(--text-primary);
}

.folder-tree-arrow.expanded {
  color: var(--text-secondary);
}

.folder-tree-arrow.placeholder {
  cursor: default;
  visibility: hidden;
}

/* 箭头用旋转代替切换图标：展开/折叠时方向连续过渡 */
.folder-tree-arrow svg {
  transition: transform 0.26s cubic-bezier(0.4, 0, 0.2, 1);
}

.folder-tree-arrow.expanded svg {
  transform: rotate(90deg);
}

/* --- 目录树节点的展开/折叠动画 ---
   用 grid-template-rows 0fr ↔ 1fr 做高度过渡：不需要测量子节点高度，
   任意层级嵌套、内容多少都能平滑展开收起 */
.ftree-node {
  display: block;
}

.ftree-children {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.26s cubic-bezier(0.4, 0, 0.2, 1);
}

.ftree-children.expanded {
  grid-template-rows: 1fr;
}

.ftree-children > .ftree-children-inner {
  overflow: hidden;
  min-height: 0;
}

/* 顶层容器始终展开，不参与动画 */
.ftree-children-root {
  display: block;
}

.ftree-children-root > .ftree-children-inner {
  overflow: visible;
}

/* 换班级加载新目录树期间的占位，避免露出上一个班级的目录 */
.ftree-loading {
  padding: 8px 16px 8px 24px;
  font-size: 0.8rem;
  color: var(--text-tertiary);
}

/* 渲染新增的节点（新建文件夹、切换班级）淡入滑出，而不是凭空出现 */
@keyframes ftreeNodeIn {
  from {
    opacity: 0;
    transform: translateX(-8px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

.ftree-node-enter {
  animation: ftreeNodeIn 0.26s ease both;
}

/* 首帧冻结动画：进页面时整棵树直接呈现最终状态，不会一起动一下 */
.folder-tree.ftree-frozen .ftree-children,
.folder-tree.ftree-frozen .folder-tree-arrow svg,
.folder-tree.ftree-frozen .ftree-node-enter {
  transition: none;
  animation: none;
}

/* ============================================
   移动端文件夹项覆盖（置于末尾确保优先级最高）
   ============================================ */
@media (max-width: 768px) {
  .folder-item {
    display: grid;
    grid-template-columns: 36px 1fr;
    gap: 4px 10px;
    align-items: center;
    padding: 12px;
    background: var(--bg-secondary);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    margin-bottom: 6px;
    cursor: pointer;
  }
  .folder-item:hover {
    background: var(--bg-tertiary);
  }
  .folder-item > .file-info {
    display: contents;
  }
  .folder-item .file-icon {
    grid-column: 1;
    grid-row: 1;
    width: 36px;
    height: 36px;
    font-size: 1.1rem;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--accent-dim);
    border-radius: var(--radius);
  }
  .folder-item .file-name {
    grid-column: 2;
    grid-row: 1;
    font-size: 0.88rem;
    font-weight: 500;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .folder-item .file-meta {
    grid-column: 1 / -1;
    font-size: 0.72rem;
    color: var(--text-tertiary);
    margin-top: 0;
  }
  .folder-item .file-actions {
    grid-column: 1 / -1;
    gap: 6px;
    flex-wrap: wrap;
    flex-shrink: 0;
    display: flex;
  }
  .folder-item .file-actions .btn {
    font-size: 0.75rem;
    padding: 4px 10px;
  }
}

@media (max-width: 480px) {
  .file-item, .folder-item {
    padding: 10px;
    grid-template-columns: 32px 1fr;
    gap: 3px 8px;
  }
  .file-item.batch-selectable-item,
  .folder-item.batch-selectable-item {
    grid-template-columns: 32px minmax(0, 1fr) 22px;
  }
  .file-item .file-icon, .folder-item .file-icon {
    width: 32px;
    height: 32px;
    font-size: 0.9rem;
  }
  .file-item .file-name, .folder-item .file-name {
    font-size: 0.86rem;
  }
  .file-item .file-meta, .folder-item .file-meta {
    font-size: 0.74rem;
  }
  .file-item .file-actions .btn, .folder-item .file-actions .btn {
    font-size: 0.76rem;
    padding: 3px 8px;
  }
}

/* ==================== 文件哈希 & 无损压缩 ==================== */

.file-hash {
  font-family: "SF Mono", "Cascadia Code", "Consolas", "Monaco", monospace;
  font-size: 0.72rem;
  color: var(--text-tertiary);
  margin-top: 2px;
  word-break: break-all;
  max-width: 320px;
}

.file-location {
  font-size: 0.72rem;
  color: var(--text-tertiary);
  margin-top: 2px;
  word-break: break-all;
  overflow-wrap: anywhere;
  max-width: 320px;
}

.file-name-cell {
  font-weight: 500;
  word-break: break-all;
  overflow-wrap: anywhere;
}

.shortcut-badge {
  display: inline-block;
  font-size: 0.7rem;
  color: var(--warning);
  background: rgba(251, 191, 36, 0.1);
  padding: 1px 6px;
  border-radius: var(--radius-sm);
  margin-left: 4px;
  vertical-align: middle;
}

.shortcut-row td {
  opacity: 0.75;
}

.flex-spacer {
  flex: 1;
}

.btn-accent {
  background: var(--accent);
  color: #fff;
  border: none;
  cursor: pointer;
  transition: background var(--transition);
}

.btn-accent:hover {
  background: var(--accent-hover);
}

.btn-accent:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.items-center {
  align-items: center;
}

/* --- Passkey / WebAuthn --- */
.passkey-separator {
  display: flex;
  align-items: center;
  margin: 16px 0;
  color: var(--text-tertiary);
  font-size: 0.85rem;
}
.passkey-separator::before,
.passkey-separator::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}
.passkey-separator span {
  padding: 0 12px;
}

.btn-passkey {
  background: var(--bg-tertiary);
  color: var(--text-primary);
  border-color: var(--border);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 10px 16px;
  font-size: 0.9rem;
  border-radius: var(--radius);
  cursor: pointer;
  transition: all var(--transition);
  width: 100%;
  border: 1px solid var(--border);
}
.btn-passkey:hover:not(:disabled) {
  background: var(--bg-elevated);
  border-color: var(--border-accent);
}
.btn-passkey:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
.btn-passkey .btn-spinner {
  display: none;
  width: 16px;
  height: 16px;
  border: 2px solid var(--text-tertiary);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}
.btn-passkey.loading .btn-spinner {
  display: inline-block;
}
.btn-passkey.loading .btn-text,
.btn-passkey.loading .passkey-icon {
  display: none;
}
.passkey-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.passkey-icon svg {
  width: 18px;
  height: 18px;
}

/* GitHub Button Styles: match the Passkey button */
.btn-github {
  background: var(--bg-tertiary);
  color: var(--text-primary);
  border: 1px solid var(--border);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 10px 16px;
  margin-top: 10px;
  font-size: 0.9rem;
  border-radius: var(--radius);
  cursor: pointer;
  transition: all var(--transition);
  width: 100%;
}

.btn-github:hover:not(:disabled) {
  background: var(--bg-elevated);
  border-color: var(--border-accent);
}

.btn-github:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.btn-github .github-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.btn-github .github-icon svg {
  width: 18px;
  height: 18px;
}

.btn-github .btn-spinner {
  display: none;
  width: 16px;
  height: 16px;
  border: 2px solid var(--text-tertiary);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}

.btn-github.loading .btn-spinner {
  display: inline-block;
}

.btn-github.loading .btn-text,
.btn-github.loading .github-icon {
  display: none;
}

.github-section-title,
.github-bindinfo-name {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.profile-section-header .github-section-title {
  flex: 1 1 auto;
  min-width: 0;
}

/* 登录方式按钮保持同一高度 */
#loginForm > #loginBtn,
#loginForm > #passkeyLoginBtn,
#loginForm > #githubLoginBtn {
  height: 48px;
  min-height: 48px;
  padding-top: 0;
  padding-bottom: 0;
}

/* GitHub Bind Info */
.github-bindinfo {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px;
  background: var(--bg-tertiary);
  border-radius: var(--radius);
  margin-bottom: 8px;
}

.github-bindinfo-name {
  font-weight: 500;
  color: var(--text-primary);
}

.passkey-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  background: var(--bg-tertiary);
  border-radius: var(--radius-sm);
  margin-bottom: 6px;
}
.passkey-item-info {
  font-size: 0.85rem;
}
.passkey-item-time {
  font-size: 0.78rem;
  color: var(--text-tertiary);
}
.passkey-item-delete {
  color: var(--danger);
  background: none;
  border: none;
  cursor: pointer;
  font-size: 0.85rem;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  transition: color var(--transition);
}
.passkey-item-delete:hover {
  color: #ff9999;
  background: rgba(248, 113, 113, 0.1);
}
.form-label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.form-label-row .form-label {
  margin-bottom: 0;
}
.passkey-admin-list {
  display: grid;
  gap: 6px;
  margin-top: 12px;
}
.passkey-admin-list .passkey-item {
  margin-bottom: 0;
}

/* ==================== 两步验证 (2FA) ==================== */
.twofa-state {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 10px 12px;
  background: var(--bg-tertiary);
  border-radius: var(--radius-sm);
  margin-bottom: 10px;
}
.twofa-badge {
  flex: 0 0 auto;
  font-size: 0.72rem;
  line-height: 1.7;
  padding: 0 8px;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-secondary);
  white-space: nowrap;
}
.twofa-badge-on {
  color: var(--success);
  border-color: rgba(74, 222, 128, 0.35);
  background: rgba(74, 222, 128, 0.12);
}
.twofa-badge-off {
  color: var(--text-tertiary);
  background: var(--bg-secondary);
}
.twofa-state-text {
  font-size: 0.82rem;
  line-height: 1.7;
  color: var(--text-secondary);
}
.twofa-qr-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 14px;
  background: var(--bg-tertiary);
  border-radius: var(--radius-sm);
}
.twofa-qr {
  width: 176px;
  height: 176px;
  padding: 6px;
  border-radius: 8px;
  overflow: hidden;
  background: #ffffff;
  box-sizing: content-box;
}
.twofa-qr svg {
  display: block;
}
.twofa-qr-hint {
  font-size: 0.78rem;
  color: var(--text-tertiary);
  text-align: center;
}
.twofa-secret-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 10px;
  padding: 10px 12px;
  background: var(--bg-tertiary);
  border-radius: var(--radius-sm);
}
.twofa-secret-text {
  min-width: 0;
}
.twofa-secret-label {
  display: block;
  font-size: 0.75rem;
  color: var(--text-tertiary);
  margin-bottom: 2px;
}
.twofa-secret {
  display: block;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  color: var(--text-primary);
  word-break: break-all;
  user-select: all;
}
/* 登录页：密码通过后切到验证码输入 */
.login-2fa-divider {
  height: 1px;
  background: var(--border);
  margin: 18px 0;
}

/* --- Profile Modal --- */
.modal-profile {
  max-width: 480px;
}


.profile-section {
  margin-bottom: 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.profile-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  cursor: pointer;
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--text-primary);
  background: var(--bg-tertiary);
  transition: background var(--transition);
  user-select: none;
}

.profile-section-header:hover {
  background: var(--bg-elevated);
}

.profile-section-header.expanded {
  border-bottom: 1px solid var(--border);
}

.profile-section-arrow {
  flex: 0 0 auto;
  margin-left: auto;
  font-size: 0.7rem;
  color: var(--text-tertiary);
  transition: transform 0.26s cubic-bezier(0.4, 0, 0.2, 1);
}

/* 展开时箭头从右转到下（旋转 90°），不再切换图标，方向连续 */
.profile-section-header.expanded .profile-section-arrow {
  transform: rotate(90deg);
}

/* --- 可折叠容器的高度动画（个人信息弹窗的各折叠面板、两步验证绑定流程等） ---
   容器本身用 grid-template-rows 0fr ↔ 1fr 做过渡，内边距放到 .collapse-content 上，
   否则收起到底时 padding 会在上方留下一条空带。
   收起时仍然保留 .hidden 类（各处逻辑靠它判断展开状态），只是让它的 display:none
   让位给折叠动画，元素留在布局里才能播过渡。
   .collapsible 与两层包裹由 JS 的 ensureCollapsible() 在运行时加上（见 app.js） */
.collapsible {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows 0.26s cubic-bezier(0.4, 0, 0.2, 1);
}

.collapsible.hidden {
  display: grid !important;
  grid-template-rows: 0fr;
}

.collapsible > .collapse-clip {
  overflow: hidden;
  min-height: 0;
  visibility: visible;
  transition: visibility 0s;
}

.collapsible.hidden > .collapse-clip {
  /* 收起动画播完再彻底隐藏，避免键盘 Tab 聚焦到看不见的输入框 */
  visibility: hidden;
  transition: visibility 0s linear 0.26s;
}

.profile-section-body {
  padding: 0;
  background: var(--bg-secondary);
}

/* 折叠面板正文的内边距（折叠流程这类普通容器不需要） */
.profile-section-body > .collapse-clip > .collapse-content {
  padding: 12px 14px;
}

.form-inline-row {
  display: flex;
  gap: 8px;
  align-items: flex-start;
}

.form-inline-row .form-input {
  flex: 1;
}

.form-inline-row .btn {
  flex-shrink: 0;
}

.form-hint {
  margin-top: 6px;
  font-size: 0.8rem;
  color: var(--text-tertiary);
}

.form-hint-success {
  color: var(--success);
}

.form-hint-error {
  color: var(--danger);
}

/* --- Recent Login Devices --- */
.login-device-item {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border-subtle);
}

.login-device-item:last-child { border-bottom: none; }
.login-device-item.is-current { background: var(--accent-dim); margin: 0 -8px; padding-left: 8px; padding-right: 8px; }
.login-device-icon { display: flex; align-items: center; justify-content: center; color: var(--accent); }
.login-device-info { min-width: 0; }
.login-device-name { color: var(--text-primary); font-size: 0.86rem; font-weight: 600; }
.login-device-meta, .login-device-time { color: var(--text-secondary); font-size: 0.76rem; margin-top: 3px; overflow-wrap: anywhere; }
.login-device-time { color: var(--text-tertiary); }
.login-device-current { color: var(--success); font-size: 0.78rem; white-space: nowrap; }
.login-device-revoke { color: var(--danger); white-space: nowrap; }

@media (max-width: 480px) {
  .login-device-item { grid-template-columns: 28px minmax(0, 1fr); }
  .login-device-action { grid-column: 2; }
}

/* ============================================
   浅色模式 — 额外覆盖
   ============================================ */
[data-theme="light"] .class-card:hover {
  box-shadow: 0 4px 20px rgba(0,0,0,0.08), 0 0 30px var(--accent-dim);
}

[data-theme="light"] .toast {
  background: rgba(255,255,255,0.95);
  box-shadow: 0 0 20px rgba(107, 92, 231, 0.1), 0 4px 16px rgba(0,0,0,0.1);
}

[data-theme="light"] .auth-card::before {
  opacity: 0.3;
}

/* --- 公告栏与更新日志 --- */
.content-type-tag {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  padding: 3px 8px;
  border: 1px solid currentColor;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1.2;
}
.content-type-announcement { color: #e08a3e; }
.content-type-log { color: var(--accent); }
.log-item-header, .log-item-meta, .log-item-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.log-item-header { justify-content: space-between; flex-wrap: wrap; }
.log-item-meta { min-width: 0; flex-wrap: wrap; }
.segmented-control { display: flex; gap: 4px; margin-bottom: 16px; padding: 4px; background: var(--bg-tertiary); border-radius: 8px; }
.segmented-control-item { flex: 1; padding: 8px 12px; border: 0; border-radius: 5px; background: transparent; color: var(--text-secondary); cursor: pointer; }
.segmented-control-item.active { background: var(--bg-primary); color: var(--text-primary); box-shadow: 0 1px 4px rgba(0,0,0,.18); }
.checkbox-label { display: flex; align-items: center; gap: 8px; color: var(--text-primary); cursor: pointer; }
.form-help { margin: 6px 0 0; color: var(--text-tertiary); font-size: .8rem; }
.log-item {
  padding: 14px 0;
  border-bottom: 1px solid var(--border-subtle);
}
.log-item:last-child {
  border-bottom: none;
}
.log-version {
  font-weight: 600;
  color: var(--accent);
  font-size: 0.95rem;
  white-space: nowrap;
}
.log-time {
  font-size: 0.78rem;
  white-space: nowrap;
}
.log-content {
  margin-top: 6px;
  color: var(--text-secondary);
  font-size: 0.88rem;
  line-height: 1.65;
  white-space: pre-wrap;
  word-break: break-word;
}

/* 弹窗内多行文本输入 */
textarea.form-input {
  resize: vertical;
  min-height: 120px;
  line-height: 1.6;
}


/* ============================================
   配置功能（按班级开关上传 / 下载）
   标题右侧一个班级数，下面是一张「设置列表」卡片：
   每行 = 左侧（班级图标 + 班名）+ 右侧两个胶囊开关。
   行与行之间是发丝分隔线，悬停给整行一层很淡的底（负外边距让底色与分隔线铺到卡片内缘，
   卡片的 overflow: hidden 会把第一 / 最后一行的方角裁回圆角）。
   开关用的还是那个 checkbox：事件（onchange）与无障碍语义不动，只是把浏览器画的方块
   改成「轨道 + 圆钮」，与设计规范第八节的胶囊开关同一套观感。
   ============================================ */
.feature-count {
  color: var(--text-tertiary);
  font-size: 0.82rem;
}

.feature-card {
  padding: 6px 20px;
  overflow: hidden;
}

.feature-list {
  display: flex;
  flex-direction: column;
}

.feature-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px 16px;
  flex-wrap: wrap;
  margin: 0 -20px;
  padding: 14px 20px;
  transition: background var(--transition);
}

.feature-row + .feature-row {
  border-top: 1px solid var(--border-subtle);
}

.feature-row:hover {
  background: var(--bg-tertiary);
}

.feature-class {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.feature-class-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  border-radius: var(--radius);
  background: var(--accent-dim);
  color: var(--accent);
}

.feature-class-name {
  font-weight: 600;
  font-size: 0.92rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.feature-toggles {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
}

.feature-toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--text-secondary);
  font-size: 0.84rem;
  cursor: pointer;
  transition: color var(--transition);
}
.feature-toggle:hover,
.feature-toggle:has(input:checked) {
  color: var(--text-primary);
}
.feature-toggle-label {
  user-select: none;
}

/* 开关本体：36×20 的轨道 + 一颗 14px 的圆钮。
   ⊙ 轨道（下面的 input 规则）与圆钮（::after）都要自己重写一遍几何：
   全局复选框那套（input[type="checkbox"] 与它的 :checked / :checked::after）
   在本文件里写在后面，凡是同分的选择器都会由它赢。
   ⚠️ 圆钮的几何必须把 :checked 也写进选择器（而不是只写 .feature-toggle input::after）：
   那条「勾选框对号」input[type="checkbox"]:checked::after 是 (0,2,1)，
   同分时后者胜，开启态就会被套上对号那套 left/top/width/height，
   圆钮变得又小又偏上、还探出轨道外（关闭态没有这条冲突，所以看着正常）。
   加上 :checked 后本规则是 (0,3,1)，稳赢。 */
.feature-toggle input[type="checkbox"] {
  position: relative;
  width: 36px;
  height: 20px;
  flex: 0 0 36px;
  border: 1px solid var(--border-hover-strong);
  border-radius: 999px;
  background: var(--bg-elevated);
  box-shadow: none; /* 压掉全局复选框那圈内阴影 */
  transition: background var(--transition), border-color var(--transition);
}
.feature-toggle input[type="checkbox"]::after,
.feature-toggle input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 2px;
  width: 14px;
  height: 14px;
  border: 0;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  transition: transform 0.24s cubic-bezier(0.22, 1, 0.36, 1);
}
.feature-toggle input[type="checkbox"]::after {
  transform: translateY(-50%);
}
.feature-toggle input[type="checkbox"]:checked {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: none;
}
.feature-toggle input[type="checkbox"]:checked::after {
  transform: translate(16px, -50%);
}

/* 空状态 */
.feature-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 34px 0;
  text-align: center;
  color: var(--text-tertiary);
}
.feature-empty small {
  font-size: 0.8rem;
  color: var(--text-tertiary);
}


/* --- Themed native checkboxes --- */
input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  width: 16px;
  height: 16px;
  margin: 0;
  flex: 0 0 16px;
  border: 1px solid var(--border-hover-strong);
  border-radius: 4px;
  background-color: var(--bg-primary);
  box-shadow: inset 0 0 0 2px var(--bg-primary);
  cursor: pointer;
}

input[type="checkbox"]:checked {
  border-color: var(--accent);
  background-color: var(--accent);
  box-shadow: inset 0 0 0 2px var(--accent);
}

input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  left: 25%;
  top: 8%;
  width: 35%;
  height: 65%;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

input[type="checkbox"]:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

#uploadModal.upload-locked .folder-selector-trigger:disabled {
  cursor: not-allowed;
  opacity: 0.65;
  background: var(--bg-tertiary);
}

/* 保留协议确认框的紧凑布局尺寸 */
.agreement-check input[type="checkbox"] {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  margin-top: 2px;
}


/* --- Mailbox --- */
.mailbox-modal {
  max-width: 680px;
  padding: 24px;
}
.mailbox-list {
  display: grid;
  gap: 8px;
  max-height: min(62vh, 560px);
  overflow-y: auto;
  padding-right: 2px;
}
.mailbox-item {
  display: grid;
  grid-template-columns: 8px minmax(0, 1fr) auto;
  align-items: start;
  gap: 12px;
  width: 100%;
  padding: 13px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-primary);
  background: var(--bg-tertiary);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background var(--transition), border-color var(--transition), transform var(--transition);
}
.mailbox-item:hover,
.mailbox-item:focus-visible {
  background: var(--bg-hover);
  border-color: var(--border-hover-strong);
  transform: translateY(-1px);
}
.mailbox-item.is-unread { border-color: var(--border-accent); }
.mailbox-item-status {
  width: 8px;
  height: 8px;
  margin-top: 7px;
  border-radius: 50%;
  background: var(--text-disabled);
}
.mailbox-item.is-unread .mailbox-item-status { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
.mailbox-item-main { min-width: 0; }
.mailbox-item-top { display: flex; align-items: center; gap: 8px; min-width: 0; }
.mailbox-item-top strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mailbox-item-preview {
  display: block;
  margin-top: 5px;
  color: var(--text-secondary);
  font-size: 0.86rem;
  line-height: 1.45;
}
/* 预览每行只占一个视觉行，过长由 CSS 截断，避免正文自己换行撑高列表项 */
.mailbox-item-preview-line {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mailbox-item-preview-more {
  color: var(--text-tertiary);
  letter-spacing: 0.1em;
}
.mailbox-item-date { color: var(--text-tertiary); font-size: 0.78rem; white-space: nowrap; }
.content-type-tag { flex: 0 0 auto; padding: 2px 7px; border-radius: 3px; font-size: 0.72rem; line-height: 1.4; }
.content-type-email { color: var(--info); background: rgba(96,165,250,0.12); }
.content-type-feedback { color: #a78bfa; background: rgba(167,139,250,0.14); }
.content-type-announcement { color: var(--warning); background: rgba(251,191,36,0.12); }
.content-type-log { color: var(--accent-hover); background: var(--accent-dim); }
.message-detail-modal { max-width: 680px; }
.message-detail-meta { margin: -8px 0 18px; color: var(--text-tertiary); font-size: 0.82rem; }
.message-detail-content { white-space: pre-wrap; overflow-wrap: anywhere; color: var(--text-primary); line-height: 1.8; }
.email-recipient-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; max-height: 220px; overflow-y: auto; padding: 4px; border: 1px solid var(--border); border-radius: var(--radius); }
.email-recipient { display: flex; align-items: center; gap: 8px; min-width: 0; padding: 8px; border-radius: var(--radius-sm); cursor: pointer; }
.email-recipient:hover { background: var(--bg-hover); }
.email-recipient input { accent-color: var(--accent); flex: 0 0 auto; }
.email-recipient span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.email-recipient small { margin-left: auto; color: var(--text-tertiary); white-space: nowrap; }
.email-selected-list { min-height: 42px; margin-top: 10px; }
.selected-email-recipient { border: 1px solid var(--border); }
.selected-email-recipient button { margin-left: auto; flex: 0 0 auto; }
.email-recipient-picker-modal { max-width: 560px; }
.email-recipient-picker-modal .email-recipient-list { max-height: min(52vh, 420px); }
.email-record-list { display: grid; gap: 8px; }
.email-record-item { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px 16px; padding: 12px 0; border-bottom: 1px solid var(--border-subtle); }
.email-record-item:last-child { border-bottom: 0; }
.email-record-item p { margin: 3px 0 0; font-size: 0.82rem; }
.email-record-content { grid-column: 1 / -1; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--text-secondary); }
@media (max-width: 560px) {
  .mailbox-modal, .message-detail-modal { padding: 18px; }
  .mailbox-item { grid-template-columns: 8px minmax(0, 1fr); }
  .mailbox-item-date { grid-column: 2; }
  .email-recipient-list { grid-template-columns: 1fr; }
}
/* --- 孤儿文件清理（管理后台 → 文件管理） --- */
.orphan-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
}
.orphan-stat {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 14px 16px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
  background: var(--bg-tertiary);
}
.orphan-stat-value {
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.orphan-stat-label { color: var(--text-tertiary); font-size: 0.78rem; }
.orphan-stat.is-ok .orphan-stat-value { color: var(--success); }
.orphan-stat.is-warn {
  border-color: rgba(251, 191, 36, 0.35);
  background: rgba(251, 191, 36, 0.07);
}
.orphan-stat.is-warn .orphan-stat-value { color: var(--warning); }
#orphanStatus:empty { display: none; }
#orphanStatus { margin-bottom: 10px; }
.orphan-row-actions {
  display: inline-flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
}

/* --- 孤儿文件清理（管理后台 → 独立弹窗） --- */
.modal-orphan {
  max-width: 880px;
}
.orphan-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 10px;
}
/* 保护期在左，两个操作按钮整体靠右 */
.orphan-toolbar #scanOrphanBtn {
  margin-left: auto;
}


/* ============================================
   移动端：放大各弹窗（独立窗口）的关闭按钮 ✕
   仅作用于移动端，桌面端样式保持不变
   ============================================ */
@media (max-width: 768px) {
  /* 弹窗右上角关闭按钮（上传 / 下载 / 个人信息等所有窗口） */
  .modal-header .btn-icon,
  .warning-modal-header .btn-icon {
    width: 40px;
    height: 40px;
    padding: 0;
    flex: 0 0 auto;
  }
  .modal-header .btn-icon svg,
  .warning-modal-header .btn-icon svg {
    width: 22px;
    height: 22px;
  }

  /* 弹窗内部的 ✕ / × 图标按钮 */
  .selected-email-recipient .btn-icon {
    font-size: 1.15rem;
    line-height: 1;
  }
  .upload-file-item-remove {
    font-size: 1.05rem;
    padding: 4px 8px;
    min-width: 32px;
    min-height: 32px;
  }
  .upload-file-item-remove svg {
    width: 18px;
    height: 18px;
  }
  .passkey-item-delete svg {
    width: 18px;
    height: 18px;
  }
}

/* ============================================
   文件预览页（viewer.html）
   ============================================ */

.viewer-body {
  /* 两行网格：顶栏 / 舞台。用网格保证舞台拿走剩下全部高度，且下界是 0，
     顶栏放不下时先挤舞台，而不是把内容挤出屏幕。
     --safe-bottom 只是给自检读的：底部小黑条（刘海机）占掉的安全区高度。 */
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  position: fixed;
  inset: 0;
  height: 100vh;
  height: 100dvh;
  /* viewer.js 会按实测的可见高度覆盖它：
     vh 是"地址栏收起时"的高度，dvh 在部分浏览器上不支持，两者都会让底部被地址栏吃掉一块 */
  height: var(--viewer-height, 100dvh);
  /* 必须显式压掉全局 body 的 min-height: 100vh —— min-height 与 height 是两个属性，
     min-height 会赢，上面这套实测高度就全被架空了（底部照样被裁掉一块）。 */
  min-height: 0;
  overflow: hidden;
  background: var(--bg-deep);
}

/* --- 预览页顶栏：沿用主站 topbar，只把文件名当作标题 --- */
.viewer-topbar {
  gap: 12px;
  padding: 0 16px;
}

.viewer-back {
  padding-left: 10px;
  flex: 0 0 auto;
}

.viewer-heading {
  min-width: 0;
}

.viewer-name {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text-primary);
  max-width: 40vw;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.viewer-meta {
  margin: 2px 0 0;
  font-size: 0.76rem;
  color: var(--text-tertiary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.viewer-toolbar {
  display: flex;
  align-items: center;
  gap: 6px;
  /* 放不下时工具栏自己横向滚动，而不是把按钮顶出屏幕或撑宽顶栏 */
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}

.viewer-toolbar::-webkit-scrollbar {
  display: none;
}

/* 顶栏按钮统一等高：文字按钮（缩放倍数 / 下载）与图标按钮同为 34px。
   文字按钮原本由 .btn-sm 的上下内边距决定高度（约 29px），和 34px 的方钮对不齐。 */
.viewer-topbar .btn {
  height: 34px;
  padding-top: 0;
  padding-bottom: 0;
}

/* 图标按钮还要把内边距钉回 0。
   全局样式在 ≤768px 里给 .btn 盖了 padding（styles.css 的 Buttons 小屏适配），
   优先级与 .btn-icon 相同但源码在后，会把 34px 的方钮挤到只剩一条缝。 */
.viewer-topbar .btn-icon {
  width: 34px;
  height: 34px;
  padding: 0;
  flex: 0 0 auto;
}

.viewer-tool-group {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.viewer-zoom-value {
  min-width: 66px;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

/* --- 舞台：文件下方是一层更深的底，衬托"浮在上面的一张纸" --- */
.viewer-stage {
  position: relative;
  /* 高度由上面那行 minmax(0, 1fr) 给足 */
  display: grid;
  /* 单一单元格：画布铺满，提示卡居中 */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  align-items: center;
  justify-items: center;
  padding: 16px;
  overflow: hidden;
  background: radial-gradient(120% 90% at 50% 0%, var(--bg-primary) 0%, var(--bg-deep) 72%);
}

.viewer-loading {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.88rem;
  color: var(--text-tertiary);
}

/* --- 空态 / 错误 / 需要用户做选择时的提示卡 --- */
.viewer-notice {
  max-width: 420px;
  text-align: center;
  color: var(--text-tertiary);
}

.viewer-notice-mark {
  display: inline-flex;
  margin-bottom: 14px;
  color: var(--text-tertiary);
  opacity: 0.75;
}

.viewer-notice h2 {
  margin: 0 0 8px;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text-primary);
}

.viewer-notice p {
  margin: 0 0 20px;
  font-size: 0.88rem;
  line-height: 1.75;
}

.viewer-notice-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
}

/* --- 图片：容器只管居中与裁剪，尺寸交给浏览器自己适配 --- */
.viewer-canvas {
  grid-area: 1 / 1;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  cursor: grab;
  touch-action: none;
}

.viewer-canvas.is-dragging {
  cursor: grabbing;
}

/* 适应窗口完全由 CSS 完成：就算 viewer.js 那边的测量出问题，图片本身也照常显示。
   用户缩放/拖动时才由 JS 叠一层 transform。 */
.viewer-image {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  /* 去掉 flex/grid 的自动最小尺寸，否则大图可能撑着不肯缩小 */
  min-width: 0;
  min-height: 0;
  transform-origin: center center;
  user-select: none;
  -webkit-user-drag: none;
  border-radius: var(--radius-sm);
  background: var(--bg-secondary);
  box-shadow: var(--shadow-lg);
}

/* --- PDF：交给浏览器内置阅读器（<object>，见 viewer.js 的能力回退），外面套一层 Lucerna 的"纸" --- */
.viewer-paper {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}

.viewer-frame {
  display: block;
  flex: 1 1 auto;
  width: 100%;
  min-height: 0;
  border: 0;
  background: var(--bg-secondary);
}

/* <object> 的回退内容：只有浏览器不能内嵌 PDF 时才会显示 */
.viewer-pdf-fallback {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  margin: 0 auto;
  padding: 20px;
}

.viewer-pdf-hint {
  flex: 0 0 auto;
  padding: 8px 12px;
  font-size: 0.76rem;
  color: var(--text-tertiary);
  text-align: center;
  border-top: 1px solid var(--border);
}

/* --- 音视频：Lucerna 卡片 + 站内播放器（不用浏览器原生控件） --- */
.viewer-media {
  /* 卡片不超出舞台单元格：高度不够时由里面的播放器让位，而不是整块溢出去被裁掉 */
  display: flex;
  flex-direction: column;
  width: min(100%, 680px);
  max-height: 100%;
  padding: 22px;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
}

/* 沉浸光感落在卡片上时只留边缘环形高光：
   表面光洒会盖在播放画面上，影响观看（与文件列表那种信息面板不同） */
.viewer-media.cursor-glow::before {
  content: none;
}

.viewer-media-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 18px;
}

.viewer-media-icon {
  display: inline-flex;
  flex: 0 0 auto;
}

.viewer-media-text {
  min-width: 0;
}

.viewer-media-name {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.viewer-media-sub {
  margin-top: 3px;
  font-size: 0.78rem;
  color: var(--text-tertiary);
}

/* ---------- 站内播放器 ---------- */

/* 音频元素本身不需要渲染：声音照常播，界面全部由下面的控制条接管 */
.viewer-media-audio {
  display: none;
}

/* 视频：画面 + 浮层控制条。宽度铺满，高度不够时被压缩（画面按 contain 留黑边） */
.viewer-video {
  position: relative;
  flex: 0 1 auto;
  min-height: 0;
  width: 100%;
  overflow: hidden;
  background: #000;
  border-radius: var(--radius);
}

.viewer-video video {
  display: block;
  width: 100%;
  height: auto;
  max-height: 100%;
  background: #000;
}

/* 中间那颗大播放键：纯装饰，点击穿透到视频容器（由容器统一处理播放/暂停） */
.viewer-video-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 64px;
  height: 64px;
  display: grid;
  place-items: center;
  color: #fff;
  background: rgba(124, 108, 240, 0.92);
  border: 0;
  border-radius: 50%;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  pointer-events: none;
  transition: opacity 0.2s ease;
}

.viewer-video.is-playing .viewer-video-play {
  opacity: 0;
}

/* 大播放键里的图标比控制条按钮大一档 */
.viewer-video-play svg {
  width: 26px;
  height: 26px;
}

/* 浮层控制条：播放中无操作就淡出，指针移入、聚焦或暂停时常驻 */
.viewer-video-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 26px 12px 10px;
  background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.72));
  opacity: 0;
  transition: opacity 0.2s ease;
}

/* 控制条的显示只由 .is-bar 控制，不用 :hover ——
   全屏时指针必然落在视频里，:hover 会永远成立，控制条就再也不消失了。
   键盘导航仍靠 :focus-within 钉住（鼠标点击的控件会在 JS 里 blur 掉，不留焦点）。 */
.viewer-video.is-bar .viewer-video-bar,
.viewer-video:focus-within .viewer-video-bar {
  opacity: 1;
}

/* 全屏时不要圆角，画面居中铺满（object-fit 让画面按比例留黑边） */
.viewer-video:fullscreen,
.viewer-video:-webkit-full-screen {
  border-radius: 0;
}

.viewer-video:fullscreen video,
.viewer-video:-webkit-full-screen video {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* 音频：控制条排在卡片里（间距由上面的头部 margin 提供） */
.viewer-player-wrap {
  flex: 0 0 auto;
}

/* 控制条本体：与上传进度条同一套「轨道 + 填充」语言 */
.viewer-player {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: rgba(0, 0, 0, 0.28);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

[data-theme="light"] .viewer-player {
  background: rgba(24, 24, 48, 0.05);
}

/* 视频里控制条已经压在渐变上，再叠一层底色会发黑 */
.viewer-video-bar .viewer-player {
  padding: 0;
  background: none;
  border-color: transparent;
}

.viewer-player-play {
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  padding: 0;
  border-radius: 50%;
}

.viewer-player-time {
  flex: 0 0 auto;
  min-width: 38px;
  font-size: 0.78rem;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* 进度 / 音量：20px 的命中区里放 6px 的轨道，手指也点得准 */
.viewer-player-track {
  position: relative;
  flex: 1 1 auto;
  height: 20px;
  display: flex;
  align-items: center;
  cursor: pointer;
  touch-action: none;
}

.viewer-player-track.is-volume {
  flex: 0 0 auto;
  width: 88px;
}

.viewer-player-track::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 6px;
  border-radius: 3px;
  background: var(--border);
}

.viewer-player-buffer,
.viewer-player-fill {
  position: absolute;
  left: 0;
  height: 6px;
  width: 0;
  border-radius: 3px;
}

.viewer-player-buffer { background: rgba(255, 255, 255, 0.16); }
[data-theme="light"] .viewer-player-buffer { background: rgba(24, 24, 48, 0.12); }

.viewer-player-fill {
  background: linear-gradient(90deg, var(--accent), #9588f7);
}

.viewer-player-knob {
  position: absolute;
  left: 0;
  width: 12px;
  height: 12px;
  margin-left: -6px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
  opacity: 0;
  transition: opacity 0.18s ease;
}

.viewer-player-track:hover .viewer-player-knob,
.viewer-player-track:focus-visible .viewer-player-knob,
.viewer-player-track.is-scrubbing .viewer-player-knob {
  opacity: 1;
}

.viewer-player-track:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.viewer-player-volume {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
}

.viewer-player-volume .btn-icon {
  width: 30px;
  height: 30px;
}

@media (max-width: 760px) {
  .viewer-player {
    gap: 8px;
    padding: 8px 10px;
  }

  /* 手机上的音量交给系统实体键，这里只留静音开关 */
  .viewer-player-track.is-volume {
    display: none;
  }

  .viewer-video-play {
    width: 56px;
    height: 56px;
  }
}

/* --- 移动端 / 窄窗口：控件收成图标、按钮放大到好点，工具栏放不下就自己横向滚动 --- */
@media (max-width: 760px) {
  .viewer-topbar {
    height: auto;
    min-height: 52px;
    padding: 6px 10px;
    gap: 8px;
    row-gap: 6px;
    /* 不让它任意折行：折成三四行会把预览区压没 */
    flex-wrap: wrap;
  }

  .viewer-back-label,
  .viewer-btn-label,
  .viewer-meta {
    display: none;
  }

  .viewer-heading {
    flex: 1 1 auto;
    min-width: 0;
  }

  .viewer-toolbar {
    gap: 4px;
  }

  .viewer-name {
    max-width: none;
    font-size: 0.88rem;
  }

  /* 手指点得准一点：整排按钮统一 40px 高，图标钮做成方形与文字钮对齐 */
  .viewer-topbar .btn {
    height: 40px;
  }

  .viewer-topbar .btn-icon,
  .viewer-topbar .viewer-back,
  .viewer-topbar #viewerDownload {
    width: 40px;
    height: 40px;
    padding: 0;
  }

  .viewer-stage {
    padding: 10px;
    /* 底部留出小黑条的安全区，内容不会压在系统手势条下面 */
    padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
  }

  .viewer-paper {
    border-radius: var(--radius);
  }

  .viewer-media {
    padding: 16px;
  }
}


/* ============================================
   移动端底部液态玻璃胶囊（Liquid Glass Dock）
   目录 / 返回上级 / 上传文件
   仅移动端显示，桌面端完全不受影响
   ============================================ */
.mobile-dock {
  display: none;
}

@keyframes dockRise {
  /* 不做透明度过渡：opacity < 1 时子层的 backdrop-filter 采不到背景，玻璃会"失灵"一下；
     也不带 translateX：胶囊现在用 left/right + auto margin 居中，不再依赖 transform */
  from { transform: translateY(130%) scale(0.96); }
  to   { transform: translateY(0) scale(1); }
}

@media (max-width: 768px) {
  /* 1) 这三个功能已挪到胶囊，原位置在移动端移除 */
  #uploadBtn2,
  .mobile-up-item {
    display: none !important;
  }

  /* 2) 底部留出胶囊高度，最后一个文件不会被压住 */
  .drive-main {
    padding-bottom: 104px;
  }

  /* 3) 胶囊本体：形状、填充、光影与折射。
     居中用 left/right + auto margin，不用 transform —— Firefox 会把带 transform 的元素
     当成 backdrop root，元素自身的 backdrop-filter 就采不到页面背后的内容（模糊失效、
     玻璃变成"透明壳"）。 */
  .mobile-dock {
    position: fixed;
    left: 0;
    right: 0;
    margin-left: auto;
    margin-right: auto;
    width: fit-content;
    bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    z-index: 150;
    display: flex;
    align-items: stretch;
    /* 每个按钮的固定宽度 = 三个按钮并排时的份额
       （左右内边距 12 + 两道间隔 8 + 左右边框 2 = 22，正好让三个按钮排满胶囊） */
    --dock-item-w: calc((min(93vw, 400px) - 22px) / 3);
    gap: 4px;
    max-width: min(93vw, 400px);
    padding: 6px;
    border-radius: 999px;
    /* 液态玻璃：填充做得很薄、靠 backdrop-filter 折射背后的内容。
       但胶囊背后常常是纯色背景（模糊纯色等于没效果），所以底色要有一点"体"，
       否则在深色页面底上会看起来像一条透明的空壳。
       斜向镜面光带已单独拆到 .mobile-dock-sheen 那层（要跟随光感触发点转动） */
    background:
      linear-gradient(180deg, rgba(255,255,255,0.16), rgba(255,255,255,0.05) 46%, rgba(255,255,255,0.025)),
      rgba(22,22,42,0.42);
    /* 折射做在胶囊本体这一层：伪元素里的 backdrop-filter 会被"内层不能再套"的规则限制住；
       并且本体不能带 transform（Firefox 会把带 transform 的元素当 backdrop root，
       那样就采不到页面背后的内容，玻璃会变透明） */
    backdrop-filter: blur(16px) saturate(210%) brightness(1.14);
    -webkit-backdrop-filter: blur(16px) saturate(210%) brightness(1.14);
    border: 1px solid rgba(255,255,255,0.26);
    /* 内壁亮边 + 暗边 + 底部内阴影：玻璃的"厚度"主要靠这几道 */
    box-shadow:
      0 16px 36px rgba(0,0,0,0.36),
      0 2px 8px rgba(0,0,0,0.20),
      inset 0 1.5px 1px rgba(255,255,255,0.55),
      inset 0 -1.5px 2px rgba(255,255,255,0.16),
      inset 6px 0 14px rgba(255,255,255,0.05),
      inset -6px 0 14px rgba(255,255,255,0.05),
      inset 0 -10px 18px rgba(0,0,0,0.10);
    animation: dockRise 0.45s cubic-bezier(0.22, 1, 0.36, 1);
    transition: opacity 0.26s ease, transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
  }

  /* 斜向镜面光带：角度（--sheen-angle）与位置（--sheen-pos）由 JS 按
     "光感触发点 → 胶囊"的连线实时写入：连线转 45° 即光带方向，
     光带再垂直 90° 就是渐变轴，所以角度取「连线角度 - 45°」。
     没有触发时退回默认的 115° / 居中，与原来的固定斜光完全一致。 */
  .mobile-dock-sheen {
    position: absolute;
    inset: 0;
    border-radius: 999px;
    overflow: hidden;
    pointer-events: none;
    --sheen-color: rgba(255,255,255,0.26);
    background: linear-gradient(
      var(--sheen-angle, 115deg),
      rgba(255,255,255,0) calc(var(--sheen-pos, 50%) - 14%),
      var(--sheen-color) var(--sheen-pos, 50%),
      rgba(255,255,255,0) calc(var(--sheen-pos, 50%) + 14%)
    );
  }

  /* 中心玻璃：本体已经做了整块折射，这一层负责让中心更"实"一点
     （在不把 transform 元素当 backdrop root 的浏览器里会再叠一层模糊，无害） */
  .mobile-dock::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 999px;
    pointer-events: none;
    backdrop-filter: blur(16px) saturate(210%) brightness(1.14);
    -webkit-backdrop-filter: blur(16px) saturate(210%) brightness(1.14);
  }

  /* 边缘折射环：mask 挖掉中间只留最外一圈，这一圈用比中心更浅的模糊做出
     "中间薄、边缘厚"的透镜感。亮度与中心保持一致（只靠模糊差表达厚度），
     再压一条与内壁同光向的白色边光——上亮、下缘一点反弹光，
     让这一环和内壁的亮边连成一条完整的玻璃边，而不是一条独立的色带 */
  .mobile-dock::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 999px;
    padding: 6px; /* 折射环的厚度 */
    pointer-events: none;
    -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
            mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
            mask-composite: exclude;
    backdrop-filter: blur(6px) saturate(190%) brightness(1.15) contrast(1.02);
    -webkit-backdrop-filter: blur(6px) saturate(190%) brightness(1.15) contrast(1.02);
    background: linear-gradient(180deg,
      rgba(255,255,255,0.34),
      rgba(255,255,255,0.12) 42%,
      rgba(255,255,255,0.04) 66%,
      rgba(255,255,255,0.14));
    transition: opacity 0.18s ease;
  }

  /* 紫色液滴层：液滴与其拖尾在这一层里被 goo 滤镜粘成"水银"（滤镜见 app/index.html 的 #dockGoo）。
     注意：这层里的图形只能用不透明颜色，goo 滤镜会把半透明像素直接吃掉 */
  .mobile-dock-goo {
    position: absolute;
    inset: 0;
    pointer-events: none;
    filter: url(#dockGoo) drop-shadow(0 5px 14px rgba(124,108,240,0.45));
  }

  /* 紫色液滴：位置与宽度由 app.js 按目标项的实际盒子设置。
     移动用带回弹的曲线 + 拉伸，配合 goo 拖尾像水银一样甩过去 */
  .mobile-dock-drop {
    position: absolute;
    top: 6px;
    bottom: 6px;
    left: 0;
    width: 0;
    border-radius: 999px;
    background: linear-gradient(180deg, #a99cff 0%, #7c6cf0 52%, #6a58e0 100%);
    opacity: 0;
    pointer-events: none;
    transition:
      transform 0.34s cubic-bezier(0.34, 1.56, 0.64, 1),
      width 0.34s cubic-bezier(0.34, 1.56, 0.64, 1),
      opacity 0.24s ease;
  }
  .mobile-dock-drop.is-on {
    opacity: 1;
  }
  /* 跟随布局动画逐帧移动时关掉过渡，避免和实际位置产生滞后 */
  .mobile-dock-drop.is-instant {
    transition: none;
  }
  /* 拖尾：液滴离开时在原地留一小滴，被 goo 滤镜拉断、融进液滴 */
  .mobile-dock-drop-tail {
    transform-origin: center;
  }

  /* 4) 胶囊内的功能项。
     所有项默认都在 DOM 里，靠 .is-off 收起（带过渡），这样以后往胶囊里加新功能
     只要沿用 .mobile-dock-item 即可自动获得同样的进出动画 */
  .mobile-dock-item {
    position: relative;
    flex: 0 0 auto;
    width: var(--dock-item-w); /* 宽度固定，不随按钮数量变化 */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    min-width: 0;
    padding: 8px 4px 7px;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: var(--text-secondary);
    font-family: inherit;
    font-size: 12px;
    line-height: 1.1;
    text-shadow: 0 1px 2px rgba(0,0,0,0.45); /* 玻璃很薄，给文字一点底衬保证可读性 */
    cursor: pointer;
    overflow: hidden; /* 收起/展开过程中裁掉内容，避免文字压到相邻项上 */
    transition:
      color 0.18s ease,
      background 0.18s ease,
      transform 0.18s ease,
      opacity 0.26s ease,
      width 0.34s cubic-bezier(0.22, 1, 0.36, 1),
      padding 0.34s cubic-bezier(0.22, 1, 0.36, 1),
      margin 0.34s cubic-bezier(0.22, 1, 0.36, 1),
      visibility 0s;
    -webkit-tap-highlight-color: transparent;
  }
  .mobile-dock-glyph {
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .mobile-dock-label {
    white-space: nowrap;
  }
  .mobile-dock-item:active {
    transform: scale(0.93);
    color: var(--text-primary);
    background: rgba(255,255,255,0.10);
    /* 按下像把玻璃压凹：内阴影收进来 */
    box-shadow: inset 0 2px 4px rgba(0,0,0,0.22), inset 0 -1px 1px rgba(255,255,255,0.22);
  }

  /* 按下时的高光涟漪：从中心扩散开的一圈亮光 */
  .mobile-dock-item::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 150%;
    aspect-ratio: 1;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(255,255,255,0.32), rgba(255,255,255,0) 68%);
    transform: translate(-50%, -50%) scale(0.2);
    opacity: 0;
    pointer-events: none;
    transition: transform 0.42s ease, opacity 0.42s ease;
  }
  .mobile-dock-item:active::after {
    transform: translate(-50%, -50%) scale(1);
    opacity: 1;
  }

  /* 按下时边缘折射环收一点光，像整块玻璃被压弯 */
  .mobile-dock:has(.mobile-dock-item:active)::after {
    opacity: 0.55;
  }
  .mobile-dock-item:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }

  /* 液滴所在的功能项：文字转白加粗，保证在紫色上的对比度 */
  .mobile-dock-item.is-current {
    color: #fff;
    font-weight: 600;
  }
  .mobile-dock-item.is-current:active {
    background: rgba(255,255,255,0.2);
  }

  /* 通用：收起胶囊里的某个功能项。
     不适用时（如已在根目录、无上传权限）加上 .is-off 即可，收起/展开都带动画。
     宽度收到 0，胶囊整体也就跟着变窄（按钮自身宽度始终不变） */
  .mobile-dock-item.is-off {
    width: 0;
    opacity: 0;
    padding-left: 0;
    padding-right: 0;
    margin-right: -4px; /* 抵消胶囊的 gap，收起的项不留下空隙 */
    pointer-events: none;
    visibility: hidden;
    transition:
      opacity 0.2s ease,
      width 0.28s cubic-bezier(0.4, 0, 1, 1),
      padding 0.28s cubic-bezier(0.4, 0, 1, 1),
      margin 0.28s cubic-bezier(0.4, 0, 1, 1),
      visibility 0s linear 0.28s; /* 等收起动画播完再真正隐藏，避免被键盘聚焦到 */
  }

  /* 所有功能项都收起时，整个胶囊一起淡出 */
  .mobile-dock.is-empty {
    opacity: 0;
    transform: translateY(130%);
    pointer-events: none;
  }

  /* 浏览器完全不支持 backdrop-filter 时，把填充做"实"，否则会是一条透明空壳
     （Firefox 的情况另见 .mobile-dock 那里的注释：它是带 transform 时不采样背景） */
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .mobile-dock {
      background:
        linear-gradient(180deg, rgba(255,255,255,0.16), rgba(255,255,255,0.05) 46%, rgba(255,255,255,0.025)),
        rgba(22,22,42,0.82);
    }
  }

  /* 5) 浅色主题下的玻璃 */
  [data-theme="light"] .mobile-dock {
    background:
      linear-gradient(180deg, rgba(255,255,255,0.44), rgba(255,255,255,0.18) 46%, rgba(255,255,255,0.12)),
      rgba(255,255,255,0.34);
    border-color: rgba(255,255,255,0.72);
    box-shadow:
      0 16px 34px rgba(24,24,48,0.12),
      0 2px 8px rgba(24,24,48,0.07),
      inset 0 1.5px 1px rgba(255,255,255,0.95),
      inset 0 -1.5px 2px rgba(255,255,255,0.55),
      inset 6px 0 14px rgba(255,255,255,0.35),
      inset -6px 0 14px rgba(255,255,255,0.35),
      inset 0 -10px 18px rgba(24,24,48,0.05);
  }
  /* 浅色下少提亮：白底再 brightness(1.14) 会过曝 */
  [data-theme="light"] .mobile-dock::before {
    backdrop-filter: blur(16px) saturate(160%) brightness(1.02);
    -webkit-backdrop-filter: blur(16px) saturate(160%) brightness(1.02);
  }
  [data-theme="light"] .mobile-dock-sheen {
    --sheen-color: rgba(255,255,255,0.55);
  }
  [data-theme="light"] .mobile-dock::after {
    backdrop-filter: blur(6px) saturate(150%) brightness(1.03);
    -webkit-backdrop-filter: blur(6px) saturate(150%) brightness(1.03);
  }
  /* 浅色主题下，按下反馈只作用于没有液滴的项，避免盖掉液滴自身的状态 */
  [data-theme="light"] .mobile-dock-item:active:not(.is-current) {
    color: var(--text-primary);
    background: rgba(24,24,48,0.06);
  }
  [data-theme="light"] .mobile-dock-item {
    text-shadow: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .mobile-dock {
    animation: none;
    transition: none;
  }
  .mobile-dock-item,
  .mobile-dock-item.is-off,
  .mobile-dock-drop,
  .mobile-dock-item::after,
  .mobile-dock::after {
    transition: none;
  }
  .drive-sidebar,
  .sidebar-backdrop {
    transition: none;
  }
  .ftree-children,
  .folder-tree-arrow svg,
  .ftree-node-enter {
    animation: none;
    transition: none;
  }
  .collapsible,
  .collapse-clip,
  .profile-section-arrow {
    animation: none;
    transition: none;
  }
  /* 点击光晕：JS 在这种系统设置下根本不挂点击委托，这里兜住"页面样式表单独被复用时"的情况 */
  .btn-halo::after {
    animation: none;
  }
}

/* ============================================
   动画效果 = 最低（设置弹窗里的开关，只存浏览器本地）
   去掉一切装饰性动效：沉浸光感、进出文件夹与目录树的过渡、弹窗与胶囊的进出、
   悬停抬升等全部变成瞬时；只保留"正在处理"的转圈与进度条扫光 —— 那是状态提示，不是装饰。
   注意：这里用最小动画时长而不是 animation: none，是为了让依赖 animationend 的逻辑照常收尾。
   ============================================ */
[data-motion="off"] *,
[data-motion="off"] *::before,
[data-motion="off"] *::after {
  animation-duration: 0.001ms !important;
  animation-delay: 0s !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.001ms !important;
  transition-delay: 0s !important;
}

/* 光带 / 进度条扫光是"正在处理"的状态提示，保留（否则会停成一动不动，看着像卡死） */
[data-motion="off"] .spinner,
[data-motion="off"] .btn-spinner {
  animation-duration: 0.6s !important;
  animation-iteration-count: infinite !important;
}
/* 三处轨道（页面遮罩 / 上传进度条 / 批量操作进度条）共用同一套接力扫动：
   只恢复时长与循环即可 —— 两条光带都不加 delay，各自的后半圈由 keyframes 里的
   opacity 负责"隐身"，所以同一时刻只有一条在扫 */
[data-motion="off"] .page-transition-track::before,
[data-motion="off"] .page-transition-track::after,
[data-motion="off"] .op-progress-pending .op-progress-track::before,
[data-motion="off"] .op-progress-pending .op-progress-track::after,
[data-motion="off"] .upload-progress-pending .upload-progress-bar-track::before,
[data-motion="off"] .upload-progress-pending .upload-progress-bar-track::after {
  animation-duration: 1s !important;
  animation-iteration-count: infinite !important;
}

/* 沉浸光感在最低动效下彻底不显示（JS 也不会给它打 .cursor-glow 标记，这里是兜底） */
[data-motion="off"] .cursor-glow::before,
[data-motion="off"] .cursor-glow::after {
  content: none;
}
[data-motion="off"] .table tbody tr.cursor-glow {
  background-image: none;
}

/* 点击光晕同理（JS 在最低动效下不生成，这里是兜底） */
[data-motion="off"] .btn-halo {
  display: none;
}

/* ============================================
   背景（铺满整个 /app：选班页 + 文件浏览页）
   图片挂在 body 的固定定位伪元素里，上面压一层与主题同色的薄纱。
   不用 background-attachment: fixed —— iOS 上它不可靠，固定层在各端表现一致。
   body 自身要让位（background: none），html 的底色正好当兜底。
   两种壁纸都在这里落地（设置里的「壁纸」模块，JS 往 body 写变量）：
     --wallpaper-image  默认缺省 = 仓库里那张；Bing 壁纸时由 JS 写成远端地址
     --wallpaper-scrim  与主题同色的薄纱
     无填充（.app-bg-none）：不铺图片也不加薄纱，只按当前主题铺一层背景色
   ============================================ */
body.app-bg {
  background: none; /* 否则 body 底色会盖住下面这层固定背景 */
}
body.app-bg::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  --wallpaper-scrim: linear-gradient(rgba(10, 10, 20, 0.38), rgba(10, 10, 20, 0.38));
  background-image:
    var(--wallpaper-scrim),
    var(--wallpaper-image, url("/backgrounds/2026-09-24-黑色熔岩海滩鸟瞰图埃尔戈尔福兰萨罗特岛加那利群岛西班牙.jpg"));
  background-size: cover, cover;
  background-position: center, center;
  background-repeat: no-repeat, no-repeat;
}
[data-theme="light"] body.app-bg::before {
  --wallpaper-scrim: linear-gradient(rgba(243, 244, 246, 0.62), rgba(243, 244, 246, 0.62));
}
/* --- 信息密集的面板改成磨砂玻璃 ---
   文件条目、文件夹条目、目录树这些必须清晰的信息都落在这些面板上：
   背后的照片先被高斯模糊抹平，再叠一层很薄的底色，文字就有稳定的底衬。
   面板透明到能看出背景的色与光，但看不出照片细节，因此不会和文字打架。 */
body.app-bg .topbar {
  background: rgba(20, 20, 34, 0.68);
  backdrop-filter: blur(16px) saturate(160%);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
}
body.app-bg .drive-sidebar {
  background: rgba(20, 20, 34, 0.55);
  backdrop-filter: blur(18px) saturate(150%);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
}
body.app-bg .drive-main {
  background: rgba(20, 20, 34, 0.42);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
}
/* 搜索框、批量操作条、面包屑（文件路径那一条）：原来都是实色填充（--bg-secondary），
   照片穿不过去。这些都是整幅的横条，一起改成「薄底 + 背景模糊」，跟面板同一层玻璃；
   边框不动，所以输入框、路径条的边界还在。 */
body.app-bg .search-input-wrap,
body.app-bg .batch-action-bar,
body.app-bg .breadcrumb {
  background: rgba(255, 255, 255, 0.07);
  backdrop-filter: blur(10px) saturate(150%);
  -webkit-backdrop-filter: blur(10px) saturate(150%);
}
/* 光标进到搜索框时略微加厚，一眼能看出正在输入 */
body.app-bg .search-input-wrap:focus-within {
  background: rgba(255, 255, 255, 0.12);
}
/* 条目悬停只加很薄的一层，别把磨砂面打成实色 */
body.app-bg .file-item:hover,
body.app-bg .folder-item:hover,
body.app-bg .breadcrumb-item:hover {
  background: rgba(255, 255, 255, 0.06);
}
/* 搜索结果卡片同理：原来是实色（--bg-secondary）的小卡片，在别的条目都透明时就它一块实色 */
body.app-bg .search-result-item {
  background: rgba(255, 255, 255, 0.05);
}
body.app-bg .search-result-item:hover {
  background: rgba(255, 255, 255, 0.09);
}
[data-theme="light"] body.app-bg .topbar {
  background: rgba(255, 255, 255, 0.72);
}
[data-theme="light"] body.app-bg .drive-sidebar {
  background: rgba(255, 255, 255, 0.62);
}
[data-theme="light"] body.app-bg .drive-main {
  background: rgba(255, 255, 255, 0.5);
}
[data-theme="light"] body.app-bg .search-input-wrap,
[data-theme="light"] body.app-bg .batch-action-bar,
[data-theme="light"] body.app-bg .breadcrumb {
  background: rgba(24, 24, 48, 0.05);
}
[data-theme="light"] body.app-bg .search-input-wrap:focus-within {
  background: rgba(24, 24, 48, 0.09);
}
[data-theme="light"] body.app-bg .file-item:hover,
[data-theme="light"] body.app-bg .folder-item:hover,
[data-theme="light"] body.app-bg .breadcrumb-item:hover {
  background: rgba(24, 24, 48, 0.05);
}
[data-theme="light"] body.app-bg .search-result-item {
  background: rgba(24, 24, 48, 0.04);
}
[data-theme="light"] body.app-bg .search-result-item:hover {
  background: rgba(24, 24, 48, 0.07);
}

/* --- 玻璃按钮 ---
   这一页铺的是照片，实色按钮（.btn-secondary 的 --bg-tertiary、.btn-primary 的 --accent）
   在磨砂面板上会像贴上去的色块，和并排的删除按钮（.btn-danger 本来就是半透明）差一截。
   这里把次要 / 主要按钮也换成「薄底」：透明到能看见背后的照片与面板色，边框不动，
   所以按钮的边界依旧清楚；不加 backdrop-filter —— 文件条目里一行就有好几个按钮，
   逐个开背景模糊太费，删除按钮也只靠半透明底就融进去了。
   「选择班级」页的按钮有自己的 --cs-glass-btn（ID 选择器优先），不受这几条影响；
   .app-bg-none（无填充）时铺的是纯色，薄底叠上去与原本的实色几乎一致，无需另开一套。 */
body.app-bg .btn-secondary {
  background: rgba(255, 255, 255, 0.10);
}
body.app-bg .btn-secondary:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.16);
}
body.app-bg .btn-ghost:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.08);
}
[data-theme="light"] body.app-bg .btn-secondary {
  background: rgba(24, 24, 48, 0.07);
}
[data-theme="light"] body.app-bg .btn-secondary:hover:not(:disabled) {
  background: rgba(24, 24, 48, 0.12);
}
[data-theme="light"] body.app-bg .btn-ghost:hover:not(:disabled) {
  background: rgba(24, 24, 48, 0.05);
}
/* 主按钮是「这一屏该点的那一个」，只在强调色上留一点透明，分量感还在。
   放在 @supports 里：不认识 color-mix 的浏览器跳过这一层，沿用全局那条实色 .btn-primary。 */
@supports (color: color-mix(in srgb, red 50%, transparent)) {
  body.app-bg .btn-primary {
    background: color-mix(in srgb, var(--accent) 86%, transparent);
  }
  body.app-bg .btn-primary:hover:not(:disabled) {
    background: color-mix(in srgb, var(--accent-hover) 90%, transparent);
    border-color: var(--accent-hover);
  }
}

/* --- 批量模式的行内复选框 ---
   原生复选框是浏览器自己画的：未勾选 = 一块实心白底，在这页的磨砂面上同样是个扎眼的小白块；
   而 appearance: none 之后直接在 input 上画对号并不可靠（不同浏览器对 input 的伪元素支持不一致），
   所以照 Bootstrap 那一类的做法走：真正的 input 透明地铺在标签上（点击 / 聚焦 / 无障碍语义都在它身上），
   外观交给旁边那个 <span class="batch-check-box">——span 的伪元素各浏览器都支持。
   弹窗里那些复选框（上传、移动 / 复制、设置）不动，仍是系统原生外观。 */
.batch-file-checkbox {
  position: relative;
}
.batch-file-checkbox input[type="checkbox"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: none;
  opacity: 0;
  cursor: pointer;
}
.batch-check-box {
  width: 16px;
  height: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.32);
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.10);
  transition: background var(--transition), border-color var(--transition);
}
.batch-check-box::after {
  content: "";
  width: 5px;
  height: 9px;
  margin-top: -2px; /* 旋转后视觉重心偏下，往上挪一点才居中 */
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) scale(0);
  transition: transform var(--transition);
}
.batch-file-checkbox:hover .batch-check-box {
  border-color: rgba(255, 255, 255, 0.5);
  background: rgba(255, 255, 255, 0.16);
}
.batch-file-checkbox input:checked + .batch-check-box {
  background: var(--accent);
  border-color: var(--accent);
}
.batch-file-checkbox input:checked + .batch-check-box::after {
  transform: rotate(45deg) scale(1);
}
.batch-file-checkbox input:focus-visible + .batch-check-box {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
[data-theme="light"] .batch-check-box {
  border-color: rgba(24, 24, 48, 0.24);
  background: rgba(24, 24, 48, 0.05);
}
[data-theme="light"] .batch-file-checkbox:hover .batch-check-box {
  border-color: rgba(24, 24, 48, 0.36);
  background: rgba(24, 24, 48, 0.09);
}

/* 无填充：不铺壁纸，只用当前主题的背景色（深色主题 → 深色，浅色主题 → 浅色） */
body.app-bg-none::before {
  background-image: none;
  background-color: var(--bg-primary);
}

/* ============================================
   「选择班级」页的玻璃化表面
   这一页铺了背景图，所以顶栏（Lucerna + 头像昵称 + 退出）、班级卡片、下方四个按钮
   （邮箱 / 设置 / 提交反馈 / 阅读协议）都改用半透明底 + 背景模糊，
   让照片透出一点又不影响文字对比度。只作用于本页，其它页面（含文件浏览页）保持实色。
   ============================================ */
#classSelectPage {
  --cs-glass: rgba(26, 26, 46, 0.72);        /* 顶栏 / 班级卡片 */
  --cs-glass-strong: rgba(26, 26, 46, 0.80); /* 悬停时略微加深 */
  --cs-glass-btn: rgba(34, 34, 58, 0.66);    /* 次要按钮 */
  --cs-glass-btn-hover: rgba(42, 42, 68, 0.78);
}
[data-theme="light"] #classSelectPage {
  --cs-glass: rgba(255, 255, 255, 0.72);
  --cs-glass-strong: rgba(255, 255, 255, 0.84);
  --cs-glass-btn: rgba(232, 234, 240, 0.70);
  --cs-glass-btn-hover: rgba(221, 224, 232, 0.80);
}

/* 顶栏：Lucerna 品牌 + 头像昵称 + 退出 */
#classSelectPage .topbar {
  background: var(--cs-glass);
  backdrop-filter: blur(16px) saturate(160%);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
}

/* 班级卡片 */
#classSelectPage .class-card {
  background: var(--cs-glass);
  backdrop-filter: blur(12px) saturate(150%);
  -webkit-backdrop-filter: blur(12px) saturate(150%);
}
#classSelectPage .class-card:hover {
  background: var(--cs-glass-strong);
}

/* 底部按钮与「管理后台」按钮（都是 .btn-secondary） */
#classSelectPage .btn-secondary {
  background: var(--cs-glass-btn);
  backdrop-filter: blur(10px) saturate(150%);
  -webkit-backdrop-filter: blur(10px) saturate(150%);
}
#classSelectPage .btn-secondary:hover:not(:disabled) {
  background: var(--cs-glass-btn-hover);
}

/* ============================================
   「选择班级」页：大屏（电子白板等）下内容通栏
   .container 默认 max-width: 1100px 且 margin: 0 auto 居中，屏幕一宽就会把
   班级块和下方四个按钮挤到屏幕中间，「管理后台」也只能贴到那 1100px 的右边缘。
   这一页改成通栏，左右留白与顶栏逐档对齐（大屏 20px / 平板 12px / 手机 8px），
   于是班级块贴左、管理后台贴右，和上方那栏一条边。
   ============================================ */
#classSelectPage .container {
  max-width: none;
  padding-left: 20px;
  padding-right: 20px;
}
@media (max-width: 768px) {
  #classSelectPage .container {
    padding-left: 12px;
    padding-right: 12px;
  }
}
@media (max-width: 480px) {
  #classSelectPage .container {
    padding-left: 8px;
    padding-right: 8px;
  }
}

/* --- 「当前版本」一行小字：贴在「选择班级」页最下方 --- */
.app-version-line {
  margin: 28px 0 4px;
  color: var(--text-tertiary);
  font-size: 0.76rem;
  letter-spacing: 0.02em;
  text-align: center;
  /* 这一页铺着壁纸，灰字直接落在照片上会糊，压一道极淡的阴影把它托起来 */
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
}
[data-theme="light"] .app-version-line {
  text-shadow: 0 1px 3px rgba(255, 255, 255, 0.6);
}

/* --- 壁纸的调色盘已移除（只保留默认壁纸 / Bing 壁纸两种模式） --- */

/* ============================================
   班级切换的「平滑」过渡（共享元素幽灵层）
   边框幽灵从起点矩形扩展到终点矩形 —— 边框一路延伸、最后淡出消失；
   另有一个文字克隆在「班级卡片名 ↔ 工具栏标题」之间飞行（反向则相反）。
   具体几何由 app.js 用 Web Animations API 驱动，这里只管视觉。
   ============================================ */
.morph-card {
  position: fixed;
  z-index: 1900;
  pointer-events: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(26, 26, 46, 0.94);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.28);
}
[data-theme="light"] .morph-card {
  background: rgba(255, 255, 255, 0.95);
  box-shadow: 0 12px 30px rgba(24, 24, 48, 0.12);
}
/* 卡片上的文件夹图标克隆：与 .class-card .class-icon 同一套外观 */
.morph-icon {
  position: fixed;
  z-index: 1901;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-dim);
  border: 1px solid var(--border-accent);
  border-radius: var(--radius-lg);
  color: var(--accent);
  font-size: 1.3rem;
}
.morph-label {
  position: fixed;
  z-index: 1901;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
}

/* ==================== 应用接入（SSO）==================== */
/* 后台「应用接入」页签与密钥弹窗用到的少量样式，授权页自己有 /sso/sso.css */

/* client_id、回调地址这类要看字符的文本 */
.sso-cell-mono {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  font-size: 0.78rem;
  overflow-wrap: anywhere;
}

.sso-scope-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.sso-scope-option {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-tertiary);
  cursor: pointer;
}

.sso-scope-option:hover {
  border-color: var(--border-hover);
}

.sso-scope-option input {
  margin: 3px 0 0;
  accent-color: var(--accent);
}

.sso-scope-name {
  display: block;
  font-size: 0.88rem;
  font-weight: 600;
}

.sso-secret-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.sso-secret-row code {
  flex: 1;
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-tertiary);
  color: var(--text-primary);
}
