/* ==================================================
   main.css —— 外壳布局样式（白色主题）
   只负责：整体布局（左导航 + iframe 项目区）、导航按钮外观和移动端菜单
   每个项目的页面样式都由自己的 index.html 独立加载。

   布局思路（参考 HuaiTu 系统）：
   - 左侧栏是一个"固定高度"的纵向 flex 容器，内部拆成两段：
       ① 顶部标题区（固定不动）
       ② 下面按钮区（flex:1 + overflow-y:auto，独立上下滑动）
   - 关键技巧：min-height: 0 和 100dvh（动态视口高度），
     否则 flex 子元素会被内容撑高，滚动条会"失灵"。
   ================================================== */

/* 清除浏览器默认的外边距和内边距，避免影响我们的布局 */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box; /* 让宽度把 padding 和 border 也算进去，计算更直观 */
}

/* html 和 body 占满整个屏幕高度 */
html, body {
  height: 100%;
}

body {
  font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
  background-color: #ffffff;  /* 白色主题：页面底色就是白色 */
  color: #333333;             /* 默认文字用深灰色，看起来柔和 */

  /* 用动态视口高度 min-height，让页面至少铺满一屏 */
  min-height: 100vh;          /* 旧浏览器的兜底写法 */
  min-height: 100dvh;         /* 新浏览器用 dvh：地址栏收起/展开时高度自动适应 */
  height: 100vh;
  height: 100dvh;
  overflow: hidden;           /* 外壳不滚动，菜单和项目内部各自滚动 */

  /* ===== 安全区域（safe-area）设置 =====
     作用：防止页面内容被手机顶部的状态栏、刘海，或者底部的导航条挡住。
     env() 是"读取环境变量"的函数；
     safe-area-inset-top / right / bottom / left 分别表示上、右、下、左
     四周的安全区距离。
     在普通浏览器（不支持安全区）上，这几行会被自动忽略，不影响显示。 */
  padding-top: env(safe-area-inset-top);
  padding-right: env(safe-area-inset-right);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
}

/* ==================== 最外层布局容器 ====================
   用弹性盒子（Flexbox）把左边导航和右边内容横着排在一起 */
.app-layout {
  display: flex;        /* 开启弹性盒子布局 */
  flex-direction: row;  /* 子元素横向排列（默认就是 row，写出来更清晰） */
  height: 100%;        /* 占满 body 扣除安全区后的空间 */
  width: 100%;          /* 宽度占满 */
  overflow: hidden;     /* 防止内容溢出出现多余的滚动条 */
}

/* ==================== 左侧菜单栏（整列容器） ==================== */
.sidebar-menu {
  /* 桌面端菜单栏宽度：112px（保持不变，右侧内容区不缩水）。
     ★ 这一版给一级按钮加了图标，但**没有加宽侧栏** ——
       做法是把按钮和字号一起收小：
         侧栏内宽 = 112 - 1(右边框) - 3×2 = 105px
         一级按钮内容宽 = 105 - 4×2 = 97px
           97 - 16(图标) - 8(展开箭头) - 2×2(间距) = 69px 放文字
           （菜单多到出现滚动条时再少 2px，即约 67px）
           13px × 5 个汉字 = 65px → 放得下，只剩约 2px 余量。
         二级按钮无图标，缩进后仍有约 80px 放文字，5 个字更宽松。
       实测：出现滚动条时所有一级 / 二级条目仍是单行，5 个字没有省略号。

     ★ 想放更长的字：优先改 .nav-btn 的 font-size（现在 13px），
       其次改 .sidebar-menu 的 width。不要先加宽侧栏 —— 它会直接吃掉内容区。 */
  width: 112px;
  flex-shrink: 0;               /* 不允许菜单栏被压缩，保持固定宽度 */
  background-color: #ffffff;    /* 白色主题：菜单栏也是白色 */
  border-right: 1px solid #eaeaea; /* 右侧加一条浅灰细线，把菜单和内容区分开 */
  display: flex;                /* 内部也用 flex：标题区 + 搜索框 + 按钮区竖着排 */
  flex-direction: column;       /* 从上到下排列 */
  min-height: 0;                /* 关键！允许这一列收缩，否则内部滚动不生效 */
  padding: 16px 3px;            /* 内边距：上下 16px，左右只留 3px，把宽度尽量让给菜单文字 */
}

/* ==================== 菜单栏顶部的固定标题区（头像 + 名称） ==================== */
.sidebar-header {
  flex-shrink: 0;               /* 不允许标题区被压缩，永远固定在顶部 */
  display: flex;                /* 头像和名称竖着排 */
  flex-direction: column;       /* 垂直方向排列：头像在上，名称在下 */
  align-items: center;          /* 头像和名称水平居中对齐 */
  /* 头像已放大到 84px（2026-10-24，学生要求"图片大一点但不动外框架"）。
     ★ 高度账（保证整个标题区高度不变，仍是 105.9px）：
         头像本身 84px，但用 margin-top: -12px 向上"借"了 12px，
         布局占位 = 84 - 12 = 72px（和 72px 版本相同）。
         再加上：上内边距 4 + gap 4 + 标题 16.9 + 下内边距 8 + 底边框 1
         = 4 + 72 + 4 + 16.9 + 8 + 1 = 105.9px。
       所以下面菜单的起始位置完全不动，侧栏宽度 112px 也不变；
       只是头像视觉上更大、顶部离侧栏上沿更近一点。 */
  gap: 4px;                     /* 头像和名称之间的垂直间距 */
  /* 左右只留 2px：这一层的内边距会直接吃掉项目名的可用宽度。
     原来左右各 8px，项目名只剩 90px 宽，7 个汉字（13px 时约 91px）就放不下了；
     头像本身是居中的，内边距大小不影响它的位置，所以这里让给文字更划算。 */
  padding: 4px 2px 8px;         /* 标题区留白 */
  /* 和下面搜索框之间留 8px。原来是 12px，加了搜索框后稍微收紧 4px，
     尽量把纵向空间还给菜单（搜索框本身还要占 32px + 8px 外边距）。 */
  margin-bottom: 8px;
  border-bottom: 1px solid #eaeaea; /* 底部一条浅灰分隔线 */
}

/* 圆形头像：放在名称上面。
   ★ 2026-10-24：按学生要求放大到 84px，同时保持标题区外框高度 105.9px 不变。
     84px 比原来的 72px 又高 12px；如果直接加高，菜单会被往下推。
     所以用 margin-top: -12px 把这 12px 从上方"借"回来：
       头像的布局占位 = 84 - 12 = 72px（和 72px 版本一样），
       视觉上头像顶部往上伸、离侧栏顶部更近，但标题位置和菜单起点完全不动。
     侧栏仍然 112px 宽；左右居中不受影响。 */
.sidebar-avatar {
  width: 84px;                  /* 头像 64 → 72 → 84px，明显更醒目 */
  height: 84px;                 /* 保持正方形 */
  margin: -12px 0 0;            /* 向上借 12px，保证标题区总高度不变 */
  border-radius: 50%;           /* 圆角 50% → 变成圆形 */
  object-fit: cover;            /* 图片按比例填满，不会被拉伸变形 */
  display: block;               /* 图片按块级元素显示，避免底部多出空隙 */
  flex-shrink: 0;               /* 头像不允许被压缩 */
  cursor: pointer;
  transition: transform 180ms ease;
}

.sidebar-avatar:hover {
  transform: scale(1.08);
}

/* ==================== 菜单栏顶部的项目名（逐字轻跳） ====================
   ★ 效果：名字里的每个字依次轻轻跳一下，像一排在跟人打招呼。
     参考的是经典的 "Bouncy Letters"（弹跳字母）效果。

   ★ 为什么每个字要单独包一层 <span>？
     CSS 动画只能作用在"元素"上，没办法只动一句话里的某一个字。
     所以 js/main.js 启动时会把「可改项目名」拆成 5 个 <span>，
     每个 span 上带一个 --i 序号，再用 animation-delay 把起跳时间错开，
     就形成了从左往右的波浪。
     （这一点和 .stagger-group 是同一个道理：纯 CSS 读不到"我是第几个"。）

   ★ 改名字时，HTML 里照旧只写一整句话，不需要自己拆 span。
     万一 js 没跑起来也不影响看：文字照常显示，只是不跳。

   ★ 想调手感，只改下面三个变量就够了：
       --title-hop    每个字跳多高（改成 0px 就等于关掉动画）
       --title-cycle  一轮多久（调大 → 更安静）
       --title-step   相邻两个字错开多少（调大 → 波浪更明显）

   宽度账（侧栏 112px）：
     可用宽度 = 112 - 3×2(侧栏内边距) - 2×2(标题区内边距) = 102px；
     13px 的汉字一个约 13px，7 个字 ≈ 91px —— 放得下；
     8 个字 ≈ 104px —— 放不下（这时 js 会退回整段文字，显示省略号、不跳）。 */
.sidebar-title {
  --title-hop: 3px;             /* 每个字跳起的高度 */
  --title-cycle: 2.4s;          /* 一轮完整的"跳 → 歇"多久 */
  --title-step: 70ms;           /* 相邻两个字的起跳时间差 */

  display: block;
  max-width: 100%;              /* 最宽只到标题区宽度，超了就交给下面的 overflow */
  font-size: 13px;              /* 比原来的 12px 大一号：小字加粗后容易糊，大一点更清楚 */
  font-weight: 700;             /* 标题加粗 */
  line-height: 1.3;             /* 行高 1.3：比默认紧一点，标题区更利落 */
  letter-spacing: 0.02em;       /* 汉字之间微微松开一点，不挤成一团 */
  color: #1a1a1a;
  white-space: nowrap;          /* 名称不换行 */
  overflow: hidden;             /* 超出部分隐藏 */
  text-overflow: ellipsis;      /* 万一放不下就显示省略号 */

  /* ★ 上下各留 4px 内边距，再用负外边距把这 4px 抵消掉。
     为什么非留不可：字往上跳 3px 时，如果盒子没有多余的空白，
     overflow:hidden 会把字的顶端切掉一条（确实会被切）。
     负外边距是为了让"文字看起来还在原来的位置"，
     不然标题会凭空往下挪 4px，和头像之间的间距也变了。 */
  padding: 4px 0;
  margin: -4px 0;
}

/* 单个字。
   为什么必须是 inline-block？
   transform（位移）对 inline 元素不生效 —— 不加这行，字是"跳"不起来的。 */
.sidebar-title__char {
  display: inline-block;
  white-space: pre;             /* 名字里如果有空格，保留它的宽度（否则空格会被吃掉） */
  animation: title-hop var(--title-cycle) infinite;
  /* 第 i 个字 = 第 i × 70ms 起跳。变量 --i 由 js 写进 style 属性 */
  animation-delay: calc(var(--i, 0) * var(--title-step));
  will-change: transform;       /* 提前告诉浏览器"它会动"，省得动画开始时才建图层 */
}

/* 弹跳曲线：
   0% ~ 20% 跳两下（先一个大跳，落地后再一个小弹），后面 80% 老老实实待着。
   ★ 为什么不一跳到底、让它一直跳？一直不停会烦人，
     "跳一下、歇一会儿"才有节奏感，也更耐看。
   ★ 为什么上升(6%)和下落(6%)一样长，但曲线不同？
     上升用 cubic-bezier(.2,.8,.3,1)：一蹬地就快，到顶前自己慢下来（像被抛上去）；
     下落用 cubic-bezier(.45,0,.8,.5)：先慢后快（像被重力拉下来）。
     两段曲线不一样，才有"真的在跳"的感觉；都用 linear 会像电梯。 */
@keyframes title-hop {
  0%   { transform: translateY(0); animation-timing-function: cubic-bezier(.2, .8, .3, 1); }
  6%   { transform: translateY(calc(var(--title-hop) * -1)); animation-timing-function: cubic-bezier(.45, 0, .8, .5); }
  12%  { transform: translateY(0); animation-timing-function: cubic-bezier(.2, .8, .3, 1); }
  16%  { transform: translateY(calc(var(--title-hop) * -0.42)); animation-timing-function: cubic-bezier(.45, 0, .8, .5); }
  20%  { transform: translateY(0); }
  100% { transform: translateY(0); }
}

/* 系统里开了"减少动态效果"的用户（晕动症、或者单纯嫌花）→ 不跳 */
@media (prefers-reduced-motion: reduce) {
  .sidebar-title__char {
    animation: none;
  }
}

/* ==================== 菜单搜索 ====================
   ★ 为什么搜索框放在 .sidebar-nav 外面？
     因为 .sidebar-nav 是独立上下滚动的。搜索框放在它上面，
     滚动菜单时搜索框不会跟着滚走，随时都能改关键词。

   ★ 宽度账（桌面端，侧栏内宽 105px）：
     105 - 2(边框) - 10(左右内边距) - 12(放大镜) - 4(间距)
       - 16(清除按钮) - 4(间距) ≈ 57px 可输入宽度。
     12px 字号下能显示约 4 个汉字；搜索用 1~2 个字就够触发过滤了。
     没有输入时清除按钮是 hidden 的，输入区还能再多 20px。
   手机端侧栏约 150px，输入区会宽松很多。 */
.sidebar-search {
  flex: 0 0 auto;               /* 不参与菜单滚动，也不允许被压扁 */
  display: flex;
  align-items: center;
  gap: 4px;
  width: 100%;
  height: 32px;
  margin: 0 0 8px;
  padding: 0 5px;
  border: 1px solid #e1e1e5;
  border-radius: 7px;
  background: #fafafa;
  transition: border-color 160ms ease, background-color 160ms ease, box-shadow 160ms ease;
}

/* 输入框聚焦时整条搜索框亮起来（输入框自己没有边框，
   所以用 :focus-within 给外层画焦点环）。颜色沿用页面的蓝色焦点色。 */
.sidebar-search:focus-within {
  border-color: #1a6fb0;
  background: #ffffff;
  box-shadow: 0 0 0 2px rgba(26, 111, 176, 0.16);
}

.sidebar-search__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 12px;
  height: 12px;
  flex: 0 0 12px;
  color: #75757c;
}

.sidebar-search__icon svg {
  display: block;
  width: 100%;
  height: 100%;
}

.sidebar-search__input {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: #1a1a1a;
  font-family: inherit;
  font-size: 12px;              /* 桌面端小一号，窄栏里能多显示几个字 */
  line-height: 1;
  -webkit-appearance: none;
  appearance: none;
}

.sidebar-search__input::placeholder {
  color: #6f6f75;               /* 对 #fafafa 的对比度 ≥ 4.5:1，占位文字也看得清 */
  opacity: 1;
}

/* 关掉浏览器自带的搜索框小叉，统一用我们自己的清除按钮 */
.sidebar-search__input::-webkit-search-cancel-button,
.sidebar-search__input::-webkit-search-decoration {
  display: none;
  -webkit-appearance: none;
  appearance: none;
}

.sidebar-search__clear {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  flex: 0 0 16px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: #75757c;
  cursor: pointer;
}

.sidebar-search__clear:hover {
  background: #ededf0;
  color: #333333;
}

.sidebar-search__clear:focus-visible {
  outline: 1px solid #1a6fb0;
  outline-offset: 1px;
}

.sidebar-search__clear svg {
  display: block;
  width: 9px;
  height: 9px;
}

.sidebar-search__clear[hidden] {
  display: none;                /* 没有输入时不显示小叉，把宽度还给输入框 */
}

.sidebar-search[hidden] {
  display: none;                /* 菜单为空时可以整块隐藏 */
}

/* 搜索没有结果时的那行字 */
.nav-empty {
  margin: 0;
  padding: 14px 6px;
  color: #6f6f75;
  font-size: 12px;
  line-height: 1.6;
  text-align: center;
}

.nav-empty[hidden] {
  display: none;
}

/* ★ 搜索时用 hidden 藏条目。
   必须显式写 display:none，因为 .nav-entry / .nav-btn 自己写了 display:flex，
   会覆盖浏览器对 [hidden] 的默认 display:none（这是个很常见的坑）。 */
.nav-entry[hidden],
.nav-btn[hidden] {
  display: none;
}

/* ==================== 菜单按钮滚动区（核心：独立上下滑动） ==================== */
.sidebar-nav {
  flex: 1;                      /* 占满标题区之外的剩余高度 */
  min-height: 0;                /* 关键！让 overflow 生效，按钮多时这里才滚动 */
  overflow-y: auto;             /* 按钮太多时，只有这一块自己上下滑动 */
  overflow-x: hidden;           /* 禁止横向滑动 */
  display: flex;                /* 按钮在里面竖着排 */
  flex-direction: column;
  gap: 3px;                     /* 最外层条目之间留 3px，比原来每项 10px 更紧凑 */
}

/* 自定义滚动条样式：极细、浅色，尽量不抢视觉（Chrome / Edge / Safari 生效）
   ★ 2026-10-24：4px → 2px。滚动条一出现会从右侧吃掉文字宽度，
     一级按钮本来就靠图标和箭头之间的余量撑 5 个字，所以这里能省 2px 很关键。
     轨道完全透明，只留一条半透明滑块；鼠标移上去才加深。 */
.sidebar-nav::-webkit-scrollbar {
  width: 2px;                   /* 只有 2px 宽，几乎是"一条线" */
}
.sidebar-nav::-webkit-scrollbar-track {
  background: transparent;      /* 轨道透明，不画一条灰带 */
}
.sidebar-nav::-webkit-scrollbar-thumb {
  background: rgba(0, 0, 0, 0.18);   /* 常态：很淡 */
  border-radius: 999px;              /* 圆头，看起来更细 */
}
.sidebar-nav::-webkit-scrollbar-thumb:hover {
  background: rgba(0, 0, 0, 0.36);   /* 鼠标靠近时才加深 */
}
/* Firefox 浏览器的滚动条样式（thin 已是最细一档，具体像素由系统决定） */
.sidebar-nav {
  scrollbar-width: thin;                              /* 细滚动条 */
  scrollbar-color: rgba(0, 0, 0, 0.22) transparent;   /* 滑块色 + 透明轨道 */
}

/* ★ Chromium / Edge / Safari 的坑：
   新版 Chrome 一旦读到标准的 scrollbar-width / scrollbar-color，
   就会**忽略** ::-webkit-scrollbar 里写的宽度，结果还是系统默认的 10px！
   （这就是为什么图上滚动条看起来那么粗，哪怕代码里写了 4px。）
   所以支持 ::-webkit-scrollbar 的浏览器里，把标准属性重置成 auto，
   让上面的 2px 自定义样式真正生效；Firefox 不支持这个选择器，会继续用 thin。 */
@supports selector(::-webkit-scrollbar) {
  .sidebar-nav {
    scrollbar-width: auto;
    scrollbar-color: auto;
  }
}

/* ==================== 菜单条目（一层容器 + 可展开的子列表） ====================
   结构：
     .nav-entry
       ├─ button.nav-btn（一级页面 或 文件夹按钮）
       └─ .nav-submenu.hidden（只有文件夹才有）
            └─ button.nav-btn.nav-btn--child × N

   ★ 宽度账（桌面端，侧栏 112px 不变）：
     侧栏内宽 = 112 - 1(右边框) - 3×2 = 105px
     一级按钮内容宽 = 105 - 4×2 = 97px
       97 - 16(图标) - 8(箭头) - 2×2(两个间距) = 69px 放文字
       出现 2px 滚动条时 = 67px；13px 的 5 个汉字 = 65px → 放得下。
     二级按钮宽 = 105 - 6(缩进) - 7(子列表左内边距) = 92px，
       再减去按钮左右 padding 10px → 文字区 82px（有滚动条时 80px）。
       80px 放 5 个汉字（65px）→ 富余更多，所以二级按钮不需要图标也够。
   ============================================================================ */
.nav-entry {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

/* ==================== 菜单按钮 ==================== */
.nav-btn {
  display: flex;                /* 图标、文字、箭头排成一行 */
  align-items: center;
  gap: 2px;                     /* 图标 / 文字 / 箭头之间只留 2px，省给文字 */
  width: 100%;
  /* 桌面端按钮高度：34px → 39px（约 +16%，2026-10-24 学生要求）。
     手机端会在下面的媒体查询里覆盖成 47px，手指更好点。
     文字靠 flex 垂直居中，所以加高不会让文字偏上。 */
  min-height: 39px;
  border: 0;
  background: transparent;
  font-family: inherit;
  text-align: left;
  line-height: 1.35;
  /* 内边距：上下 8px 保证点击区域仍然好按；左右只留 4px。
     侧栏依然是 112px，能塞下「图标 + 5 个字」靠的是：
     字号从 15px 收到 13px + 左右内边距从 8px 收到 4px + 压缩间距。 */
  padding: 8px 4px;
  margin-bottom: 0;             /* 间距交给 .sidebar-nav / .nav-entry 的 gap 统一管 */
  color: #555555;               /* 文字颜色：深灰色 */
  font-size: 13px;              /* 小一档，换来图标和 5 个字并排的空间 */
  border-radius: 6px;           /* 圆角 */
  cursor: pointer;              /* 鼠标放上去变小手，提示可以点击 */
  user-select: none;            /* 防止文字被鼠标选中 */
  flex-shrink: 0;               /* 按钮不被压扁 */
  /* 过渡里加上 transform，是为了下面"按下去缩一点"能平滑回弹 */
  transition: background-color 0.2s, color 0.2s,
              transform 220ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* 一级按钮的图标：固定 16×16，里面放一张图片。
   ★ 为什么在 CSS 里写死 16×16，而不是让图片按原始尺寸显示？
     因为菜单栏只有 112px 宽，原图可能是 360×360，
     直接显示会把整个侧栏撑坏。这里相当于给图片一个固定相框，
     图片按比例缩进相框里（object-fit: contain）。 */
.nav-btn__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  flex: 0 0 16px;               /* 不许被压窄，否则图标会变形 */
  opacity: 0.68;                /* 平时稍微退后一点，文字才是主角 */
  transition: opacity 0.2s;
}

/* 图片本体：占满 16×16 的相框，保持比例，不拉伸。
   object-fit: contain = 完整装进去，宁可有留白也不裁切。 */
.nav-btn__icon-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* DSH2.gif 是白底的占位图。
   multiply 混合模式会把白色"乘"成背景色，只在灰色悬停/选中底上
   不会留下一个 16×16 的白方块。
   ★ 换成自己的透明底 PNG / WebP / GIF 后，这段不会触发（代码只给占位图加 is-placeholder）。
   ★ 如果你的新图也是白底，可以把这条规则改成 .nav-btn__icon-img。 */
.nav-btn__icon.is-placeholder .nav-btn__icon-img {
  mix-blend-mode: multiply;
}

/* 文字：只有它会伸缩。放不下时显示省略号（而不是把侧栏撑宽）。
   ★ 为什么不让文字换行？侧栏只有 112px，一换行按钮就变高、整列节奏全乱，
     所以这里锁死单行；默认的 5 个字是放得下的。 */
.nav-btn__label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* 文件夹右边的小箭头：收起朝右，展开转 90° 朝下。
   10px → 8px：一级按钮的文字区因此多出 2px，5 个字更稳。 */
.nav-btn__chevron {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 8px;
  height: 8px;
  flex: 0 0 8px;
  opacity: 0.5;
  transition: transform 160ms ease, opacity 160ms ease;
}

.nav-btn__chevron svg {
  display: block;
  width: 100%;
  height: 100%;
}

.nav-btn--folder[aria-expanded="true"] .nav-btn__chevron {
  transform: rotate(90deg);
  opacity: 0.75;
}

/* 文件夹展开时给一个极浅的底，和普通页面区分开。
   这个灰度比"当前页"的 #f0f0f0 浅，所以不会和高亮打架。 */
.nav-btn--folder[aria-expanded="true"] {
  background-color: #fafafa;
  color: #333333;
}

/* 当前页住在这个文件夹里：图标和文字变清楚，提示"当前页在这个夹子里"。 */
.nav-btn--folder.has-active-child {
  color: #1a1a1a;
}

.nav-btn--folder.has-active-child .nav-btn__icon {
  opacity: 1;
}

/* ==================== 文件夹里的子条目 ====================
   二级条目没有图标（用户明确要求），靠缩进 + 一条竖线表达层级。
   ★ 这条竖线现在加粗到 2px，并且颜色可以**按每个目录单独设置**：
     · 默认 / 兜底颜色写在下面 var(--nav-submenu-line, #356b57) 里；
     · 想改某一个目录，去 js/main.js 的 MENU_ITEMS 里给它写 lineColor；
     · 竖线用伪元素画，不占布局宽度，所以加粗以后 5 个字仍然放得下。
   为什么默认用深绿而不是深蓝？这个页面的键盘焦点圈已经是蓝色（#1a6fb0），
   再用蓝色画层级线，容易让人误以为是"聚焦 / 选中"；深绿更安静，
   只表达"这些条目属于同一个目录"。 */
.nav-submenu {
  position: relative;           /* 给下面的 ::before 竖线当定位基准 */
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0 0 2px 6px;
  /* 原来靠 border-left 画线，只有 1px 而且会被内容宽度算进去；
     现在改成绝对定位的伪元素，可以放心加到 2px，不挤压文字。 */
  padding-left: 7px;
  /* 展开时轻微下落淡入；收起是瞬间的（靠 hidden），所以只需要一条进场动画 */
  animation: submenu-enter 160ms ease-out both;
}

/* 目录线本体：2px 宽、上下略微缩进、圆角端点。
   颜色由 --nav-submenu-line 决定，这个变量由 js/main.js 按目录写进来。 */
.nav-submenu::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1px;
  bottom: 2px;
  width: 2px;
  border-radius: 2px;
  background: var(--nav-submenu-line, #356b57);
}

.nav-submenu[hidden] {
  display: none;                /* hidden 必须真的不显示，否则会占着高度留一块空白 */
}

@keyframes submenu-enter {
  from {
    opacity: 0;
    transform: translateY(-3px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.nav-btn--child {
  /* 桌面端二级按钮：30px → 35px（约 +16%，跟着一级一起加高） */
  min-height: 35px;
  padding: 6px 4px 6px 6px;
  color: #666666;
  font-size: 13px;
  border-radius: 5px;
}

/* ==================== 菜单按钮的交互状态 ==================== */

/* 鼠标悬停在按钮上时的效果。文件夹和子条目共用。 */
.nav-btn:hover {
  background-color: #f5f5f5;  /* 悬停时淡淡的浅灰背景 */
}

.nav-btn:hover .nav-btn__icon {
  opacity: 0.9;
}

/* 按下去的反馈：缩小到 96% + 内阴影（视觉上凹进去）。
   为什么时长要单独写短一点？按下去必须"立刻"响应，
   0.2 秒的延迟会让人觉得按钮是软的、不跟手；松手回弹则慢一些，
   这一快一慢的差别就是"物理感"的来源。
   （曲线里的 1.56 大于 1，所以松手时会轻微过冲一下再回正 —— 也就是"弹"。） */
.nav-btn:active {
  transform: scale(0.96);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.12);
  transition-duration: 90ms;
}

/* 当前被选中的按钮：浅灰底 + 深色文字加粗。
   配色是中性灰而不是彩色 —— 白色主题下这样更干净，也不刺眼。 */
.nav-btn.active-btn {
  background-color: #f0f0f0;  /* 浅灰背景，比悬停色稍深，能区分开 */
  color: #1a1a1a;             /* 近黑色文字，对比度足够高，看得清楚 */
  font-weight: 600;           /* 文字加粗，更醒目 */
}

/* 鼠标悬停在"当前选中"的按钮上时不再变深，避免和选中状态混在一起 */
.nav-btn.active-btn:hover {
  background-color: #eaeaea;
}

/* ===== 键盘焦点圈（无障碍的重要一环）=====
   :focus-visible 的意思是"只有用键盘操作时才显示这圈线"。
   为什么不用普通的 :focus？因为鼠标点一下也会触发 :focus，
   那样每点一次按钮都会留下一圈难看的边框。
   用 :focus-visible：鼠标用户看不到它，键盘用户又能清楚知道焦点在哪，两边都照顾到。
   颜色 #1a6fb0 在白色底上对比度约 5.2:1，达到无障碍标准 AA。 */
.nav-btn:focus-visible,
.menu-toggle:focus-visible,
.sidebar-avatar:focus-visible {
  outline: 2px solid #1a6fb0;
  outline-offset: 2px;
  border-radius: 8px;
}

/* 侧栏只有 112px 宽，而 .sidebar-nav 又开了 overflow-x: hidden。
   焦点圈如果向外扩 2px，左右两侧会被滚动容器裁掉；
   所以菜单按钮把焦点圈改到内侧（-2px），键盘用户能看到完整一圈。 */
.nav-btn:focus-visible {
  outline-offset: -2px;
  border-radius: 6px;
}

/* 头像本身就是圆的，焦点圈也跟着圆，看起来才不别扭 */
.sidebar-avatar:focus-visible {
  border-radius: 50%;
}

/* ==================== 右侧内容区 ==================== */
.content-area {
  flex: 1;                    /* 占满剩余的所有宽度（弹性布局的关键） */
  min-height: 0;              /* 关键！允许收缩，让内容区自己滚动 */
  min-width: 0;
  position: relative;
  overflow: hidden;           /* 项目自己的网页负责自己的滚动 */
  padding: 0;
  background-color: #ffffff;  /* 白色主题：内容区也是白色 */
}

/* ==================== 点头像弹出的卡片 ==================== */
.profile-modal {
  position: fixed;
  z-index: 400;
  inset: 0;
  display: grid;
  place-items: center;
  padding: max(18px, env(safe-area-inset-top)) max(18px, env(safe-area-inset-right)) max(18px, env(safe-area-inset-bottom)) max(18px, env(safe-area-inset-left));
  overflow: auto;
}

.profile-modal[hidden] {
  display: none;
}

.profile-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(9, 12, 17, 0.66);
  opacity: 0;
  transition: opacity 260ms ease;
}

.profile-modal.is-open .profile-backdrop {
  opacity: 1;
}

.profile-dialog {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: min(92vw, 470px);
  max-height: calc(100dvh - 30px);
  padding: 28px 26px 24px;
  border: 1px solid rgba(255, 255, 255, 0.24);
  border-radius: 18px;
  background: linear-gradient(145deg, rgba(35, 39, 46, 0.98), rgba(12, 14, 18, 0.98));
  box-shadow: 0 28px 80px rgba(0, 0, 0, 0.48), 0 0 0 1px rgba(255, 255, 255, 0.05) inset;
  opacity: 0;
  transform: translate(-44vw, -44vh) rotate(-30deg) scale(0.2);
  transform-origin: 0 0;
}

.profile-modal.is-open .profile-dialog {
  animation: profile-dialog-in 620ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.profile-modal.is-closing .profile-dialog {
  animation: profile-dialog-out 220ms ease-in forwards;
}

@keyframes profile-dialog-in {
  0% { opacity: 0; transform: translate(-44vw, -44vh) rotate(-30deg) scale(0.2); }
  68% { opacity: 1; transform: translate(14px, -8px) rotate(4deg) scale(1.02); }
  100% { opacity: 1; transform: translate(0, 0) rotate(0) scale(1); }
}

@keyframes profile-dialog-out {
  0% { opacity: 1; transform: translate(0, 0) rotate(0) scale(1); }
  100% { opacity: 0; transform: translate(-18vw, -18vh) rotate(-12deg) scale(0.76); }
}

.profile-close {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: rgba(255, 255, 255, 0.76);
  font-size: 28px;
  font-weight: 300;
  line-height: 1;
  cursor: pointer;
  transition: background 160ms ease, color 160ms ease, transform 160ms ease;
}

.profile-close:hover {
  background: rgba(255, 255, 255, 0.12);
  color: #ffffff;
  transform: rotate(8deg);
}

/* ==================== 卡片标题：香槟金 + 流动的高光（Text Shimmer） ====================
   参考学生给的「文本闪烁」写法：把渐变"裁"进文字里
   （background-clip: text + 文字本身透明），再让背景横向移动，
   看起来就是一道光从字面上扫过去。

   ★ 这里叠了两层渐变（先写的那层在上面）：
     ① 扫光  —— 一条很窄的近白光带（约一个字宽），位置靠 animation 移动；
     ② 底色  —— 竖向的香槟金：上亮、下略深，像金属被上方光照到。
     只有底色的话是"金色字"，加上扫光才变成"金色字在反光"。

   ★ 为什么不照着参考做成 2.6s 无缝循环？
     参考那个是 40px 的大标题，一直闪不刺眼；
     这里只有 18px，而且弹窗会一直开着 —— 一直闪就成了"呼吸灯"，很吵。
     所以改成"扫一下、停五秒"：6.8s 一轮，其中约 2s 在扫，剩下时间静止。

   ★ 亮带为什么做得这么窄（12% ≈ 一个字宽）？
     亮带如果和字一样宽，扫过去时整行一起变亮，看起来像"整体闪了一下"；
     只有窄成一道光，才是"光从字面上滑过去"。

   ★ 兼容兜底：不支持"渐变裁字"的浏览器，@supports 里那整段都不生效，
     标题就退回成普通的米白色（上面的 color），不会变成隐形文字。 */
.profile-title {
  margin: 0 0 14px;
  color: #f2e7d1;                       /* 兜底色：渐变裁字不支持时用这个 */
  font-family: Georgia, "Times New Roman", serif;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 0.08em;
}

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .profile-title {
    background-image:
      /* ① 扫光：两侧完全透明，中间一段近白 —— 它只是"提亮"，不改变字本身的颜色 */
      linear-gradient(
        100deg,
        rgba(255, 255, 250, 0) 44%,
        rgba(255, 255, 250, 0.98) 50%,
        rgba(255, 255, 250, 0) 56%
      ),
      /* ② 底色：竖向香槟金（上亮下深） */
      linear-gradient(
        180deg,
        #f8f0e0 0%,
        #dcc79b 55%,
        #c2a469 100%
      );
    /* 第一层比文字宽 2.3 倍，才有"光带在里面跑"的空间；第二层铺满即可 */
    background-size: 230% 100%, 100% 100%;
    background-repeat: no-repeat, no-repeat;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    animation: profile-title-shimmer 6.8s linear infinite;
  }
}

/* 0% → 30%：光带从右往左扫过整行（约 2s）；
   30% → 100%：停在原位休息 —— 这时光带已经跑到文字左边外面，看不见了。
   ★ 两个 background-position 分别对应上面两层：只有第一层在动。 */
@keyframes profile-title-shimmer {
  0%   { background-position: 100% 50%, 0% 0%; }
  30%  { background-position: 0% 50%, 0% 0%; }
  100% { background-position: 0% 50%, 0% 0%; }
}

/* 卡片舞台：aspect-ratio 直接跟着原图走，这样"整张图"能完整放进来 */
.profile-card-stage {
  /* 原图尺寸是 3276×5824 = 1 : 1.7778。这里用同样的比例，
     图片就能严丝合缝地铺满，不需要裁掉任何一部分。 */
  aspect-ratio: 3276 / 5824;
  height: min(66dvh, 600px);
  max-height: calc(100dvh - 152px);
  /* 透视：数值越小，3D 越"冲"。1400 太平、900 有点过头，1000 是截图比对出来的中间值。 */
  perspective: 1000px;
  position: relative;   /* 给下面的"载入光罩"做定位基准 */
}

/* ==================== 卡片的「载入光罩」 ====================
   作用：图片还没就绪时，先给用户一个好看的占位，而不是一片空白。

   ★ 为什么需要它（实测依据）：
     卡片图是 19.1 MP 的大图（两张共 38 MB）。如果用户一进页面就点头像，
     图片可能**还在下载**（4G 下要 47 秒），或者**正在解码**。
     这段时间里如果不给反馈，用户会以为"点了没反应"。

   ★ 但要说清楚它的边界：光罩只能盖住"等待"，盖不住"卡顿"。
     实测过：图片解码/绘制那一下会让主线程冻住约 217 毫秒，
     期间**任何 rAF/CSS 动画都会一起停住**（连粒子也会定住）。
     所以真正解决卡顿的是 js/main.js 里的**空闲预解码**（见那边的注释），
     光罩负责的是"图片还在路上"和"解码还没轮到我"这两种等待。 */
.profile-card-veil {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  place-items: center;
  border-radius: 14px;

  /* 缓慢旋转的彩色渐变 —— 用的是和 .glow-border 同一套手法 */
  background:
    linear-gradient(#1b1e24, #1b1e24) padding-box,
    conic-gradient(
      from var(--glow-angle),
      #2dd4bf, rgba(255, 255, 255, 0.06), #a78bfa, rgba(255, 255, 255, 0.06), #2dd4bf
    ) border-box;
  border: 1px solid transparent;
  animation: glow-spin 6s linear infinite, veil-breathe 3.2s ease-in-out infinite;

  color: rgba(255, 255, 255, 0.72);
  font-size: 13px;
  letter-spacing: 0.06em;
  transition: opacity 360ms ease;
}

/* 图片就绪后，光罩淡出并让开点击 */
.profile-card-stage.is-ready .profile-card-veil {
  opacity: 0;
  pointer-events: none;
}

@keyframes veil-breathe {
  0%, 100% { box-shadow: 0 0 0 rgba(45, 212, 191, 0); }
  50%      { box-shadow: 0 0 28px rgba(45, 212, 191, 0.22); }
}

/* 尊重"减少动态效果"：光罩保留（它是信息，不是装饰），但不再呼吸旋转 */
@media (prefers-reduced-motion: reduce) {
  .profile-card-veil {
    animation: none;
  }
}

/* ==================== 卡片的 3D 倾斜层（跟鼠标轻轻转） ====================
   参考学生给的「倾斜 3D」：鼠标在卡片范围内移动时，卡片朝着鼠标方向轻轻转过去
   （rotateX / rotateY），移开再慢慢回正。

   ★ 参数为什么和参考不一样：
     · 参考 maxTilt = 14°，那是给 280px 小卡片写的；
       我们这张在屏幕上约 330×586，面积大得多，14° 会晃得头晕 —— 取 **7°**。
     · 参考直接改 transform；这里只把角度写进 CSS 变量，
       让 CSS 的 transition 负责过渡，js 更短，回正也更顺。

   ★ 为什么单独一层，而不是直接转 .profile-card？
     .profile-card 身上已经有"翻面"的 rotateY(180deg) 了。
     两个旋转挤在同一个 transform 上会互相覆盖、也难维护；
     拆成两层：**这一层管"歪"，里面那层管"翻面"**，可以叠加，互不干扰。
     这一层必须 preserve-3d，否则里面的 3D 翻面会被压成平面。

   ★ 为什么只有鼠标能用？
     触摸屏上没有"悬停"，pointermove 在手指滑动时也会触发，卡片会卡在奇怪的角度。
     js 里已经把 touch 过滤掉了，这一层只对鼠标 / 触控笔生效。 */
.profile-card-tilt {
  --tilt-rx: 0deg;          /* 上下歪：鼠标在下 → 卡片上沿向后仰 */
  --tilt-ry: 0deg;          /* 左右歪 */
  --tilt-lift: 0px;         /* 悬停时朝观察者推近一点（透视下会有轻微放大） */

  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transform:
    rotateX(var(--tilt-rx))
    rotateY(var(--tilt-ry))
    translateZ(var(--tilt-lift));
  transition: transform 260ms cubic-bezier(0.2, 0.7, 0.3, 1);
  will-change: transform;
}

/* 鼠标进入卡片区域：推近 26px —— 透视 1000px 下视觉放大约 2.7%，
   比直接写 scale 更像"卡片离你近了一点"，而且不会把描边也一起放大。 */
.profile-card-stage:hover .profile-card-tilt {
  --tilt-lift: 26px;
}

.profile-card {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform 760ms cubic-bezier(0.2, 0.78, 0.22, 1);
  will-change: transform;
}

.profile-card.is-flipped {
  transform: rotateY(180deg);
}

.profile-card-face {
  position: absolute;
  inset: 0;
  /* ★ 这里故意**不加** overflow:hidden 和 border-radius：
     原图自己带透明边框和圆角造型（透明像素占 15.7%，中间还有镂空），
     图片本身就负责"造型"。如果我们再套一层圆角裁切，等于把设计裁掉一次，
     而且在深色背景上会露出一圈矩形硬边。
     去掉裁切后，透明区域自然透出弹窗底色，原设计才完整。 */
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

.profile-card-front { transform: rotateY(0deg); }
.profile-card-back { transform: rotateY(180deg); }

.profile-card-image {
  display: block;
  width: 100%;
  height: 100%;
  /* ★ contain = "完整装进去"，绝不裁切。
     （原来是 cover = "铺满容器"，图片比容器宽/窄时会被切掉边缘。
       改成 contain 后，整张图 100% 可见 —— 代价是容器比例和图片比例不一致时
       会出现留白。所以上面 .profile-card-stage 也改成了原图比例，
       两者配合，既完整又不留白。） */
  object-fit: contain;
  /* 用 drop-shadow 而不是 box-shadow：
     它沿着图片"不透明区域的轮廓"投影，能跟着圆角造型走；
     box-shadow 只会投一个矩形，反而会把原本隐藏的矩形边暴露出来。 */
  filter: drop-shadow(0 18px 30px rgba(0, 0, 0, 0.45));
}

/* ==================== 卡片上的「翻面」按钮（双框 / Double Border） ====================
   ★ 写法参考了学生给的 Double Border 结构：
     外层是一圈"相框"（2px 边 + 5px 内边距 + 深色底），
     里面再套一层细边 + 斜向渐变的"面板"，两层之间留出 5px 的缝。
     层次感靠"两条边 + 一道缝"撑起来，不堆阴影 —— 在深色弹窗上更干净，
     也不会像实心胶囊那样显得又粗又闷。

   ★ 颜色为什么不用参考里的荧光绿？
     这张卡片本身是黑金 / 香槟色调（卡片图、标题 #f2e7d1 都偏暖），
     再放一个荧光绿就有三个互相打架的强调色了。
     所以内框换成卡片上的香槟金；想换回绿色，只改下面两个变量：
       --flip-accent: rgba(228, 255, 84, .4);
       --flip-accent-bright: rgba(228, 255, 84, .75);

   ★ 状态怎么区分（深色背景上不能只靠明暗）：
     悬停 = 内框变亮一档 + 落影铺大
     按下 = 整体下沉 1px + 内层面板变亮（js 手动加 .is-pressed 也是这个效果）
     键盘焦点 = 外面再加一圈蓝色 outline（深色上必须看得见） */
.profile-flip-button {
  --flip-accent: rgba(224, 190, 130, 0.60);         /* 内框：常态（卡片上的香槟金） */
  --flip-accent-bright: rgba(232, 202, 148, 0.95);  /* 内框：悬停 */

  display: inline-block;
  margin-top: 22px;
  padding: 5px;                                     /* ★ 双框之间那道缝的宽度 */
  border: 2px solid rgba(255, 255, 255, 0.16);      /* 外框：只是一层浅浅的"相框" */
  border-radius: 999px;                             /* 胶囊形 */
  background: #141414;                              /* 外框底色，比弹窗再深一点 */
  color: #fafafa;
  font: inherit;
  cursor: pointer;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);       /* 和背景"离开一点" */

  transition:
    border-color 200ms ease,
    box-shadow 200ms ease,
    transform 140ms cubic-bezier(0.34, 1.4, 0.64, 1);
}

/* 里面那层：真正装图标和文字的面板 */
.profile-flip-button__inner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-width: 122px;
  min-height: 38px;
  padding: 8px 20px;
  border: 1px solid var(--flip-accent);             /* 内框：第二层边 */
  border-radius: 999px;
  /* 斜向渐变只要一层很淡的暖光，别抢文字 */
  background: linear-gradient(135deg, rgba(242, 231, 209, 0.05) 0%, transparent 62%);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;

  transition: border-color 200ms ease, background 200ms ease;
}

/* 悬停：外框提亮、内框变亮、落影铺大 —— 整体"抬起来"一档 */
.profile-flip-button:hover {
  border-color: rgba(255, 255, 255, 0.28);
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.5);
}

.profile-flip-button:hover .profile-flip-button__inner {
  border-color: var(--flip-accent-bright);
  background: linear-gradient(135deg, rgba(242, 231, 209, 0.09) 0%, transparent 62%);
}

/* 按下 / 翻面触发：整体下沉 1px，内层面板被"点亮" */
.profile-flip-button:active,
.profile-flip-button.is-pressed {
  transform: translateY(1px);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45);
  transition-duration: 80ms;                        /* 按下去要立刻响应 */
}

.profile-flip-button:active .profile-flip-button__inner,
.profile-flip-button.is-pressed .profile-flip-button__inner {
  border-color: var(--flip-accent-bright);
  background: linear-gradient(135deg, rgba(242, 231, 209, 0.16) 0%, rgba(242, 231, 209, 0.03) 62%);
}

/* 键盘焦点：深色上必须有一圈看得见的环，否则键盘用户不知道焦点在哪 */
.profile-flip-button:focus-visible {
  outline: 2px solid #6fb3ff;
  outline-offset: 3px;
}

/* 图标：翻面时转半圈（aria-pressed 变成 true 时由最后一条规则转动） */
.profile-flip-icon {
  display: inline-block;
  font-size: 17px;
  line-height: 1;
  transition: transform 600ms cubic-bezier(0.2, 0.78, 0.22, 1);
}

.profile-flip-button[aria-pressed="true"] .profile-flip-icon { transform: rotate(180deg); }

@media (max-width: 768px) {
  .profile-dialog {
    width: min(92vw, 390px);
    padding: 24px 18px 20px;
    border-radius: 16px;
  }

  .profile-card-stage {
    height: min(66dvh, 560px);
    max-height: calc(100dvh - 126px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .profile-modal.is-open .profile-dialog,
  .profile-modal.is-closing .profile-dialog {
    animation: none;
    opacity: 1;
    transform: none;
  }

  .profile-card,
  .profile-flip-icon { transition-duration: 1ms; }

  /* 3D 倾斜：直接锁死成平的。
     这一类"跟着鼠标动"的效果是晕动症的重灾区（相当于画面在手里晃），
     所以不是"变慢"，而是**完全不转**（js 那边也会因为同一条 media query 不启用）。 */
  .profile-card-tilt {
    transition: none;
    transform: none !important;
  }

  /* 翻面按钮：关掉"按下去位移"和所有过渡。
     但**保留双框外观**（外框、内框、中间那道 5px 的缝、淡淡的渐变，
     全都是静态样式，不是动效），否则按钮会退化成一块平色，
     那属于把设计一起删掉了。 */
  .profile-flip-button {
    transition: none;
  }

  .profile-flip-button:active,
  .profile-flip-button.is-pressed {
    transform: none;
  }

  /* 卡片标题：停下那道光，但保留香槟金渐变（字面还是金色、有明暗）。
     光带停在文字右外侧，所以静止时看到的是"干净的金色标题"，不是一道卡住的高光。 */
  .profile-title {
    animation: none;
    background-position: 0% 50%, 0% 0%;
  }
}

/* 每个独立项目占满右侧区域，项目内部的 CSS 不会污染外壳。 */
.page-frame {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  background: #ffffff;
}

/* iframe 加载或入口出错时显示在上层的状态文字。 */
.page-status {
  position: absolute;
  z-index: 2;
  top: 16px;
  left: 50%;
  max-width: calc(100% - 32px);
  overflow-wrap: anywhere;
  transform: translateX(-50%);
  padding: 8px 12px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.94);
  color: #555555;
  font-size: 13px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
  pointer-events: none;
  transition: opacity 160ms ease;
}

.page-status.ready {
  opacity: 0;
}

.page-status.error {
  color: #a23b3b;
}

/* ==================== 「暂无项目」引导提示 ====================
   当 js/main.js 顶部的 MENU_ITEMS 是空的时候，右边会显示这一块，
   告诉使用者"这里为什么是空的、接下来该做什么"。
   没有它的话右边就是一片白，会让人以为网页坏了。 */
.empty-state {
  position: absolute;
  top: 50%;
  left: 50%;
  width: min(560px, calc(100% - 48px));
  transform: translate(-50%, -50%);   /* 配合 top/left 50% 做居中 */
  padding: 28px 30px;
  border: 1px solid #eaeaea;
  border-radius: 14px;
  background: #ffffff;
  color: #333333;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.06);
}

.empty-state-title {
  margin: 0 0 10px;
  font-size: 19px;
  font-weight: 700;
  color: #1a1a1a;
}

.empty-state-text {
  margin: 0 0 14px;
  font-size: 14px;
  line-height: 1.7;
  color: #555555;
}

/* 三步操作说明：左边留出位置给序号 */
.empty-state-steps {
  margin: 0 0 16px;
  padding-left: 20px;
  font-size: 14px;
  line-height: 1.9;
  color: #555555;
}

.empty-state-steps li {
  margin-bottom: 4px;
}

/* code 标签：等宽字体 + 浅灰底，一眼能看出"这是要照着敲的东西" */
.empty-state code {
  padding: 1px 5px;
  border-radius: 4px;
  background: #f4f4f5;
  color: #b0335a;
  font-family: Consolas, "Courier New", monospace;
  font-size: 13px;
}

/* 最下面那行示例配置：整行高亮，并且允许横向滚动，
   免得手机屏幕窄的时候这句长代码把布局撑破 */
.empty-state-code {
  margin: 0;
  padding: 12px 14px;
  overflow-x: auto;
  border-radius: 8px;
  background: #f7f7f8;
  color: #333333;
  font-family: Consolas, "Courier New", monospace;
  font-size: 13px;
  white-space: nowrap;
}

@media (max-width: 768px) {
  .empty-state {
    padding: 22px 18px;
    border-radius: 12px;
  }

  .empty-state-title {
    font-size: 17px;
  }
}

/* ==================== 手机端悬浮菜单按钮（桌面端隐藏） ==================== */
.menu-toggle {
  display: none;              /* 桌面端不显示，手机端才显示 */
  position: fixed;            /* 悬浮：固定在屏幕上，不随页面滚动 */
  flex-direction: column;     /* 动图和文字竖着排 */
  align-items: center;        /* 水平居中 */
  gap: 4px;                   /* 动图和文字之间的间距 */
  z-index: 200;               /* 层级最高，浮在所有内容之上 */
  cursor: pointer;            /* 小手光标 */
  background: transparent;    /* 透明背景 */
  user-select: none;          /* 禁止选中文字 */
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  touch-action: none;         /* 拖动时不触发页面滚动 */
  opacity: 1;
  transition: opacity 200ms ease;   /* 侧栏展开时淡出，配合下面的 .is-hidden */

  /* ★ 写死宽度，非常关键，不是为了好看。
     原因：里面的文字是 white-space: nowrap，不想被拆行；
     而"按钮居中"这类定位需要用按钮宽度来算。
     如果不写宽度，按钮宽度就完全由内容撑开 —— 有时会量到 0
     （在 display:none → 显示 的切换瞬间、或字体还没排好版时），
     一旦量到 0，"贴右边"的算式就会把它算到屏幕外面。
     写死 76px 之后，宽度永远确定，怎么算都不会出错。
     下面的文字最长是"点击展开菜单"（12px 字号约 72px），76px 刚好放得下。 */
  width: 76px;
}

/* 悬浮按钮里的动图：缩小约 40%（64px → 38px），圆形 */
.menu-toggle-img {
  width: 38px;
  height: 38px;
  border-radius: 50%;         /* 圆形 */
  object-fit: cover;          /* 动图按比例填满圆形，不变形 */
  display: block;
  pointer-events: none;       /* 点击事件统一交给外层按钮，避免点中图片 */
}

/* 悬浮按钮下方的提示文字 */
.menu-toggle-text {
  font-size: 12px;
  color: #ffffff;             /* 白色文字 */
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7); /* 文字阴影，保证各种背景都看得清 */
  white-space: nowrap;        /* 文字不换行 */
  /* 字比按钮宽时自动缩字号（反正就 6 个字，一般用不上，纯属保险） */
  max-width: 100%;
}

/* 长按拖动时的视觉反馈：稍微变透明 */
.menu-toggle.dragging {
  opacity: 0.8;
}

/* 侧栏展开时把悬浮按钮淡出。
   为什么？侧栏一滑出来就会盖住按钮所在的位置，两者叠在一起很乱；
   而且这时候用户已经在看菜单了，不需要再点这个按钮
   （想收起菜单：点右边的灰色遮罩、或者点任意一个菜单项都行）。
   注意用的是 opacity 而不是 display:none —— 
   display:none 会让元素尺寸变成 0，会触发"尺寸变化 → 重新计算位置"的多余循环；
   opacity 只是变透明，尺寸不变，逻辑更干净。 */
.menu-toggle.is-hidden {
  opacity: 0;
  pointer-events: none;   /* 透明了就别再让它接收点击，免得误触 */
}

/* 只有「老秦的英语」的 19 个系列页：手机端隐藏悬浮菜单按钮，
   菜单改由页面搜索框左侧的头像打开（见 pages/_course/js/features/catalog/topbar.js）。
   其它项目 / 模板空页面不在 body.is-laoqin-page 下，悬浮按钮照常显示。 */
body.is-laoqin-page .menu-toggle {
  display: none !important;
}

/* ==================== 遮罩层（桌面端隐藏） ==================== */
.overlay {
  display: none;              /* 桌面端不显示 */
  position: fixed;
  inset: 0;                   /* 铺满整个屏幕 */
  background: rgba(0, 0, 0, 0.4); /* 半透明黑色 */
  z-index: 90;                /* 在侧边栏下面、悬浮按钮下面 */
  opacity: 0;
  visibility: hidden;         /* 默认不可见也不可点击 */
  transition: opacity 250ms ease, visibility 250ms ease;
}

/* 展开侧边栏时，遮罩层显示 */
.overlay.show {
  opacity: 1;
  visibility: visible;
}

/* ==================== 手机端适配（响应式） ====================
   当屏幕宽度小于等于 768px（手机 / 平板竖屏）时，应用下面的样式 */
@media (max-width: 768px) {

  /* 左侧菜单栏变成"侧边滑出面板"：默认藏在屏幕左侧外 */
  .sidebar-menu {
    position: fixed;           /* 固定定位，脱离原来的 flex 布局 */
    top: 0;
    left: 0;
    width: 40%;                /* 展开时占屏幕宽度的 40% */
    max-width: 300px;          /* 但最宽不超过 300px，避免太宽 */
    height: 100vh;
    height: 100dvh;            /* 动态视口高度 */
    z-index: 100;              /* 在遮罩层之上 */
    transform: translateX(-100%); /* 默认整体移到屏幕外 → 看不见 */
    transition: transform 250ms ease; /* 滑入滑出的过渡动画 */
    box-shadow: none;
  }

  /* 展开状态：滑回原位 */
  .sidebar-menu.open {
    transform: translateX(0);
    box-shadow: 8px 0 30px rgba(0, 0, 0, 0.15); /* 右侧投影，更立体 */
  }

  /* 悬浮菜单按钮：手机端才显示。
     这里先给一个"安全"的默认位置：从右上角再往左移 30% 屏宽。
     万一 JS 因为任何原因没跑起来，按钮也还在屏幕里，不会跑到屏幕外看不见。
     真正的精确位置由 js/main.js 计算后覆盖（它会换成 px，并且考虑安全区域）。
     ★ 这里的 30% 和 js/main.js 的 MENU_DEFAULT_LEFT_SHIFT 是同一个值，
       改默认位置时两处要一起改。 */
  .menu-toggle {
    display: flex;
    top: 14px;
    left: auto;
    right: calc(14px + 30%);
  }

  /* 遮罩层：手机端显示 */
  .overlay {
    display: block;
  }

  /* 右侧内容区：占满整个宽度（侧边栏已脱离文档流） */
  .content-area {
    padding: 0;
  }

  /* 手机端搜索框：高度加到 40px，手指更容易点中。
     ★ 输入框字号用 16px：iOS Safari 对小于 16px 的输入框会在聚焦时
       自动放大整个页面，非常烦；16px 可以避免这个行为。 */
  .sidebar-search {
    height: 40px;
    padding: 0 6px;
  }
  .sidebar-search__icon {
    width: 13px;
    height: 13px;
    flex-basis: 13px;
  }
  .sidebar-search__input {
    font-size: 16px;
  }
  .sidebar-search__clear {
    width: 22px;
    height: 22px;
    flex-basis: 22px;
  }
  .sidebar-search__clear svg {
    width: 10px;
    height: 10px;
  }
  .nav-empty {
    padding: 16px 6px;
    font-size: 13px;
  }

  /* 手机端按钮区：面板本身会展开到 40% 屏宽，比桌面宽，
     所以这里只需要把上下点击区域留够，不用再为 5 个字抠宽度。
     ★ 2026-10-24：手机端按钮再加高约 30%，方便手指点击：
       一级 36px → 47px（+30.6%），二级 32px → 42px（+31.3%）。
       桌面端仍保持 39px / 35px（在基础规则里，比更早的 34/30 多约 16%），
       不受这段媒体查询影响。
       按钮里的图标和文字靠 flex 垂直居中，所以只改 min-height 不会让文字偏上。 */
  .nav-btn {
    font-size: 13px;
    min-height: 47px;
    padding: 9px 6px;
  }
  .nav-btn--child {
    min-height: 42px;
    padding: 7px 6px;
  }
  .sidebar-title {
    font-size: 12px;          /* 手机端面板会展开得更宽（40% 屏宽），字号只比桌面小一号 */
  }
}


/* 切换项目会创建新的 iframe，因此入场动画随每次切换重播。 */

/* 定义动画：从"透明 + 往下 8px"，变成"不透明 + 回到原位" */
@keyframes content-enter {
  from {
    opacity: 0;                  /* 完全透明（先看不见） */
    transform: translateY(8px);  /* 向下挪 8px，做出"轻轻浮上来"的感觉 */
  }
  to {
    opacity: 1;                  /* 完全显示 */
    transform: translateY(0);    /* 回到原位 */
  }
}

/* 只动画项目框架，不移动加载状态提示。 */
.page-frame {
  animation: content-enter 260ms ease-out both;
}

/* ==================== 无障碍考虑（专业项目都会做的事） ====================
   如果用户在操作系统里打开了"减少动态效果"
   （有些人对页面动效敏感，会头晕、不适），
   就干脆不要动画，直接显示内容。
   这样既照顾了用户，又不影响功能。 */
@media (prefers-reduced-motion: reduce) {
  .page-frame {
    animation: none;             /* 关掉动画，内容直接出现 */
  }

  /* 菜单按钮的按压缩放也一并关掉。
     注意保留悬停时的背景变色 —— 那是"状态提示"，不是"动效"，
     删掉反而会让用户不知道鼠标在哪一项上。 */
  .nav-btn {
    transition: background-color 0.2s, color 0.2s;
  }

  .nav-btn:active {
    transform: none;
    box-shadow: none;
  }

  /* 文件夹展开的淡入、箭头旋转都属于动效 → 一起停掉。
     aria-expanded 和 hidden 的逻辑不动，所以功能完全不受影响。 */
  .nav-submenu {
    animation: none;
  }

  .nav-btn__chevron {
    transition: none;
  }
}
