/* ==========================================================
   赤色红骑の小窝 · 浅色毛玻璃主题
   ----------------------------------------------------------
   分节目录：搜下面的节名就能跳到对应段落，不用从头读整个文件。
     基础与变量块      :root 主题色/圆角/阴影/波浪 mask（真值在 site-config.js，
                       这里只是它没加载时的兜底）+ 通用重置（*、[hidden]、body、a/img/button）
     背景层            全屏背景图轮播 + 毛玻璃波浪 + 下半屏磨砂
     粒子 & 花瓣       首页飘浮粒子、樱花花瓣
     导航               顶部固定导航条 + 移动端折叠菜单
     布局               .page / .wrap / .card / .card-grid 等通用容器与卡片底座
     首页卡片进场 / 退场 路由切换时首页卡片的入场退场动画
     首页仪表盘         首页四张卡：日历 / 内容分类 / 站点统计 / 音乐
     项目               项目页卡片
     照片墙             相册网格 + 相册详情 + 照片网格
     图片放大           点照片弹出的灯箱
     音乐               音乐页：播放器卡片 + 曲目列表 + 进度条
     灵感引用           灵感引用页卡片
     迷你播放器         右下角常驻的小播放条
     页脚               页脚
     响应式             各断点适配（媒体查询集中在末尾）
   ========================================================== */
:root {
  --text: #2c2a30;
  --text-soft: #5f5c68;
  --text-mute: #8b8794;
  --accent: #e05a52;
  --accent-soft: #f08b7f;
  --glass: rgba(255, 255, 255, 0.5);
  --glass-strong: rgba(255, 255, 255, 0.68);
  --glass-border: rgba(255, 255, 255, 0.7);
  --shadow: 0 10px 34px rgba(48, 42, 60, 0.1);
  --shadow-lg: 0 18px 50px rgba(48, 42, 60, 0.16);
  /* 2026-10-29：卡片悬停态的「更强一档」。阴影范围按老大的要求收到 0.7：
     模糊半径 70px → 49px，偏移和浓度不动（22px / .3），阴影更聚拢更实，不像原来那样往外糊。 */
  --glass-hover: rgba(255, 255, 255, 0.82);
  --shadow-hover: 0 22px 49px rgba(48, 42, 60, 0.3);
  --radius: 24px;
  --radius-sm: 16px;
  --nav-h: 64px;
  /* 顶部清晰区高度 = 一整屏：进入页面时整屏都是清晰图，卡片整块排在水面之下。
     波浪 / 毛玻璃的基准已改挂视口底边（见 .frost-wave / .frost-body），不再跟这个值走 */
  --hero-h: 100vh;              /* 顶部清晰区高度 */

  /* 首页卡片基准值 x：首页四张卡的尺寸基准。
     真正要改的值在 site-config.js 里（那里会盖掉这里），这里是兜底 */
  --home-card-x: clamp(300px, 30vw, 380px);
  --home-card-gap: clamp(16px, 1.6vw, 20px);

  /* 波浪 mask：同一浪形横向错相位叠三层，全画进同一张 SVG，元素只挂一层 mask-image。
     错相位才看得出「多层交叉」（同相位会糊成一条）；不做竖向渐隐，波浪毛玻璃一路实到底。
     正常由 site-config.js 按 SITE_WAVE 现场生成并覆盖；这里是 JS 没跑到时的兜底。 */
  --wave-merged: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%201200%20200'%20preserveAspectRatio%3D'none'%3E%3Cpath%20d%3D'M-1200%2080C-1050%2034%20-750%2034%20-600%2080C-450%20106%20-150%20106%200%2080C150%2034%20450%2034%20600%2080C750%20106%201050%20106%201200%2080C1350%2034%201650%2034%201800%2080C1950%20106%202250%20106%202400%2080L2400%20200L-1200%20200Z'%20fill%3D'%23fff'%20fill-opacity%3D'1'%2F%3E%3Cpath%20d%3D'M-1600%2088C-1450%2042%20-1150%2042%20-1000%2088C-850%20114%20-550%20114%20-400%2088C-250%2042%2050%2042%20200%2088C350%20114%20650%20114%20800%2088C950%2042%201250%2042%201400%2088C1550%20114%201850%20114%202000%2088C2150%2042%202450%2042%202600%2088L2600%20200L-1600%20200Z'%20fill%3D'%23fff'%20fill-opacity%3D'0.74'%2F%3E%3Cpath%20d%3D'M-2000%2096C-1850%2050%20-1550%2050%20-1400%2096C-1250%20122%20-950%20122%20-800%2096C-650%2050%20-350%2050%20-200%2096C-50%20122%20250%20122%20400%2096C550%2050%20850%2050%201000%2096C1150%20122%201450%20122%201600%2096C1750%2050%202050%2050%202200%2096C2350%20122%202650%20122%202800%2096L2800%20200L-2000%20200Z'%20fill%3D'%23fff'%20fill-opacity%3D'0.57'%2F%3E%3C%2Fsvg%3E");
}

* { box-sizing: border-box; }

/* 保证 hidden 属性优先级高于组件的 display */
[hidden] { display: none !important; }

html, body { height: 100%; }   /* 整页滚动：滚轮 / 触摸在页面任意位置都生效 */

body {
  /* 背景上移量：JS 按滚动距离逐帧写入 --rise(px)，--wave-shift 是它的负值，
     波浪区 + 毛玻璃区整体上移，逐步把顶部清晰区盖掉 */
  --rise: 0px;
  --wave-shift: calc(-1 * var(--rise));
  /* 屏幕上还剩多少清晰区 = 清晰区原高 - 已上移量（标题层用它决定大小与隐藏） */
  --hero-live: calc(var(--hero-h) - var(--rise));
  margin: 0;
  color: var(--text);
  font-family: "PingFang SC", "HarmonyOS Sans SC", "Microsoft YaHei", "Hiragino Sans GB", system-ui, -apple-system, sans-serif;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  min-height: 100%;
  background: #f4f1ee;
}

a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }
button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }

/* ---------------- 背景层 ---------------- */
.bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  background: #efeae6;
}
/* 背景图：全站只用这一层，一张图 cover 铺满整屏（不留空白），顶部对齐 */
.bg-photo {
  position: absolute;
  left: 0; right: 0; top: 0; bottom: 0;
  overflow: hidden;
}
.bg-photo .bg-slide { background-position: center top; }
.bg-slide {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  transition: opacity 2s ease-in-out;
  will-change: opacity;
}
.bg-slide.is-active { opacity: 1; }

/* 毛玻璃层：多层波浪（不同透明度 / 模糊 / 相位）＋ 下半屏磨砂 */
.bg-frost {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
/* 波浪 + 磨砂：整块毛玻璃只用一个元素、一个 backdrop-filter（2026-10-07 四改）。
   为什么不再拆两个元素：两个 backdrop-filter 相邻时，后画的会把先画的那层玻璃
   糊进自己的背景里，接缝一带就白一截；盒子对齐也一样。合成一个元素后没有第二块
   玻璃可糊，也没有「采样区域不一致」的问题，接缝从根上不存在。
   mask 分两层：① 波浪（固定 15.6vh，只占盒子最上面那条，横向平铺）；
             ② 从 15.6vh 处起实心的竖渐变 → 波浪以下一路实心到屏幕底。
   mask 默认按 add 叠加，两层合起来正好是「波浪边 + 下方实心」。
   两层都是写死的静态值、不带会取不到值的 var，不会触发「整条 mask 静默作废」。 */
.frost-wave {
  position: absolute;
  left: 0; right: 0;
  bottom: 0;
  height: calc(var(--rise, 0px) + 15.6vh);
  background: rgba(255, 253, 251, 0.46);
  -webkit-backdrop-filter: blur(16px) saturate(1.15);
  backdrop-filter: blur(16px) saturate(1.15);
  -webkit-mask-image: var(--wave-merged, none), linear-gradient(to bottom, rgba(0, 0, 0, 0) calc(15.6vh - 0.5px), #000 calc(15.6vh - 0.5px));
  mask-image: var(--wave-merged, none), linear-gradient(to bottom, rgba(0, 0, 0, 0) calc(15.6vh - 0.5px), #000 calc(15.6vh - 0.5px));
  -webkit-mask-size: 1200px 15.6vh, 100% 100%;
  mask-size: 1200px 15.6vh, 100% 100%;
  -webkit-mask-repeat: repeat-x, repeat-x;
  mask-repeat: repeat-x, repeat-x;
  -webkit-mask-position: 0 0, 0 0;
  mask-position: 0 0, 0 0;
  /* 速度取 SITE_WAVE.drift[0]（兜底 64s）；只平移 X，第二层是竖渐变、横移无影响 */
  animation: waveDrift var(--wave-drift, 64s) linear infinite;
}
/* .frost-body 已删除（2026-10-07 四改）：磨砂那半块并进 .frost-wave 的第二层 mask，
   整块毛玻璃只留一个 backdrop-filter，接缝问题从根上不存在。 */
/* 单层 mask：一次动画平移整张 SVG，走满一个 1200px 周期正好无缝衔接 */
@keyframes waveDrift {
  from {
    -webkit-mask-position: 0 0;
    mask-position: 0 0;
  }
  to {
    -webkit-mask-position: -1200px 0;
    mask-position: -1200px 0;
  }
}

/* 缓慢流动的浅色渐变，给画面一点呼吸感 */
/* 背景图上的叠加装饰层（.bg-flow 流动渐变 + .orb 两团大光斑）已按老大要求移除：
   它们铺在清晰区上方，会把背景图染偏、压低清晰度。需要恢复时从备份
   style.css.bak_before_clearfix_20261003 取回即可 */

/* ---------------- 粒子 & 花瓣 ---------------- */
#particles {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
.petals {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}
.petal {
  position: absolute;
  top: -8vh;
  width: 10px;
  height: 10px;
  background: radial-gradient(circle at 30% 30%, #ffd8d2, #ffb3a7);
  border-radius: 60% 10% 60% 10%;
  opacity: 0.75;
  animation: petalFall linear infinite;
}
@keyframes petalFall {
  0% { transform: translate(0, -10vh) rotate(0deg); opacity: 0; }
  10% { opacity: 0.8; }
  85% { opacity: 0.8; }
  100% { transform: translate(14vw, 112vh) rotate(420deg); opacity: 0; }
}

/* ---------------- 导航 ---------------- */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 50;
  background: rgba(255, 255, 255, 0.42);
  -webkit-backdrop-filter: blur(20px) saturate(1.3);
  backdrop-filter: blur(20px) saturate(1.3);
  border-bottom: 1px solid rgba(255, 255, 255, 0.55);
  transition: background 0.4s ease, box-shadow 0.4s ease;
}
.nav.is-scrolled {
  background: rgba(255, 255, 255, 0.68);
  box-shadow: 0 4px 22px rgba(48, 42, 60, 0.07);
}
.nav-inner {
  width: 90%;
  max-width: var(--page-w, 1650px);
  margin: 0 auto;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.brand {
  font-size: 19px;
  font-weight: 800;
  letter-spacing: -0.4px;
  color: #26232b;
  white-space: nowrap;
}
.brand-mark { color: var(--accent); margin: 0 3px; }
.brand:hover { color: var(--accent); }

.nav-links { display: flex; gap: 26px; font-size: 14px; font-weight: 600; }
.nav-links a {
  position: relative;
  color: var(--text-soft);
  padding: 6px 0;
  transition: color 0.25s ease;
}
.nav-links a:hover { color: var(--accent); }
.nav-links a.is-active { color: var(--accent); }
.nav-links a.is-active::after {
  content: "";
  position: absolute;
  bottom: 0; left: 50%;
  width: 4px; height: 4px;
  margin-left: -2px;
  border-radius: 50%;
  background: var(--accent);
  animation: dotPulse 1.8s ease-in-out infinite;
}
@keyframes dotPulse {
  0%, 100% { transform: scale(1); opacity: 0.9; }
  50% { transform: scale(1.5); opacity: 0.5; }
}

.nav-toggle { display: none; width: 34px; height: 34px; padding: 7px; }
.nav-toggle i {
  display: block;
  height: 2px;
  margin: 5px 0;
  border-radius: 2px;
  background: #3a3742;
  transition: transform 0.3s ease, opacity 0.3s ease;
}
.nav-toggle.is-open i:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle.is-open i:nth-child(2) { opacity: 0; }
.nav-toggle.is-open i:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* ---------------- 布局 ---------------- */
/* 内容区：文档流里的普通块，随整页滚动自然上移。
   z-index 10 保证卡片浮在毛玻璃/波浪之上，不做任何封顶，
   卡片再多也能正常滚上去（背景层在下面自己上移让位） */
main {
  position: relative;
  flex: 1;
  z-index: 10;                 /* 压在背景层之上：卡片浮在毛玻璃区上面 */
}
.wrap {
  width: 90%;
  max-width: var(--page-w, 1650px);
  margin: 0 auto;
  /* 初始布局不变：内容从清晰区下方起排（清晰区高度 + 一段留白） */
  padding: calc(var(--nav-h) + 46px) 0 30px;
}
.page { display: none; }
.page.is-active { display: block; animation: pageIn 0.6s cubic-bezier(0.22, 1, 0.36, 1); }
@keyframes pageIn {
  from { opacity: 0; transform: translateY(18px); }
  to { opacity: 1; transform: translateY(0); }
}

.page-title { font-size: 27px; margin: 0 0 6px; letter-spacing: -0.5px; }
.page-sub { margin: 0 0 26px; color: var(--text-mute); font-size: 14px; }

/* 顶部清晰区文字层：承担大标题 + 页面介绍 */
.hero {
  /* 标题层的挂载点：空块，只负责撑出顶部清晰区，卡片在它的下方 */
  position: relative;
  height: var(--hero-h);
  /* 关键：body 是 flex 列容器，默认 flex-shrink:1 会把这个空块压扁成 0，
     一压扁 main/卡片起点就冲到画面上方（看着像浮在屏幕中间），
     而 main 又被 flex:1 填满一屏 → 文档总高刚好等于视口，页面完全滚不动 */
  flex: none;
  display: flex;
  align-items: center;
  pointer-events: none;        /* 别挡住面板的点击与滚动 */
  transition: height 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
.hero-inner {
  /* 钉在视口里：背景层是固定的，标题如果不固定，一滚动就被文档带走滚出屏幕 */
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: -1;                 /* 压在内容卡片之下、页面背景之上 */
  width: 90%;
  max-width: var(--page-w, 1650px);
  margin: 0 auto;
  /* 高度 = 屏幕上还剩的清晰区：JS 逐帧写 --rise，标题层跟着上移 + 收紧 */
  height: var(--hero-live);
  padding-bottom: min(5vh, calc(var(--hero-live) * 0.12));
  display: flex;
  flex-direction: column;
  justify-content: center;
  pointer-events: none;        /* 不挡住底下卡片的点击与滚动 */
  transition: opacity 0.3s ease;
}
/* 清晰区被毛玻璃完全盖住 → 标题 + 小字一起淡出隐藏 */
body.is-hero-gone .hero-inner { opacity: 0; visibility: hidden; }
.hero-title {
  margin: 0 0 16px;
  /* 字号随清晰区收缩（--hero-progress 0→1 由 JS 逐帧写），最多缩到约 42% */
  font-size: calc(clamp(28px, 4.2vw, 50px) * (1 - var(--hero-progress, 0) * 0.58));
  line-height: 1.14;
  letter-spacing: -1.2px;
  color: #fff;
  text-shadow: 0 2px 4px rgba(28, 22, 36, 0.35), 0 8px 34px rgba(28, 22, 36, 0.45);
  margin-bottom: calc(16px * (1 - var(--hero-progress, 0) * 0.5));
}
.hero-title .brand-mark { color: #ffd7d0; }
.hero-sub {
  margin: 0;
  min-height: 1.7em;
  font-size: calc(clamp(14px, 1.7vw, 20px) * (1 - var(--hero-progress, 0) * 0.5));
  color: rgba(255, 255, 255, 0.94);
  letter-spacing: 0.3px;
  text-shadow: 0 1px 3px rgba(28, 22, 36, 0.4), 0 4px 22px rgba(28, 22, 36, 0.45);
}

/* 滚动垫板：只负责把文档垫高，保证清晰区能被完整推走（高度由 JS 计算） */
.scroll-pad { width: 100%; height: 0; pointer-events: none; }
/* 旧的 is-shrink 收缩态规则已移除：改由 --rise 连续上移驱动（见 body 变量与第 9 节 JS） */
/* 内容改为固定面板内部滚动后，文档不再滚动，页脚后的垫板也不需要了 */
html, body { overflow-anchor: none; }
.hero-caret { display: none; }
.hero.is-typing .hero-caret {
  display: inline-block;
  width: 2px;
  height: 1.05em;
  margin-left: 3px;
  vertical-align: -0.16em;
  background: currentColor;
  animation: caretBlink 1.05s steps(1, end) infinite;
}
@keyframes caretBlink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
.hero.is-switching .hero-inner { animation: heroIn 0.7s cubic-bezier(0.22, 1, 0.36, 1); }
@keyframes heroIn {
  from { opacity: 0; transform: translateY(16px); }
  to { opacity: 1; transform: translateY(0); }
}

/* 通用毛玻璃卡片 */
.card {
  background: var(--glass);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  /* 鼠标跟随倾斜：JS 逐帧写 --card-rx / --card-ry（鼠标压住哪个角，哪个角就往里沉），
     --card-lift / --card-scale 负责原本的悬停浮起。默认值全是"没倾斜"，
     所以触屏、未悬停时和原来完全一样。
     首页那四张卡的进场 / 退场动画不写在这里，而是关键帧直接动 transform（见下面"首页卡片进场"） */
  transform: perspective(900px)
             translate3d(0, var(--card-lift, 0px), 0)
             rotateX(var(--card-rx, 0deg))
             rotateY(var(--card-ry, 0deg))
             scale(var(--card-scale, 1));
  transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.45s ease, background 0.45s ease;
}
.card:hover {
  --card-lift: -4px;
  --card-scale: 1.05;   /* 2026-10-29：悬停整体放大到 1.05（原来是倾斜时 JS 写的 1.02） */
  box-shadow: var(--shadow-hover);
  background: var(--glass-hover);
}
/* 鼠标停在卡片内时换成短过渡：跟手要快。离开时摘掉这个类，
   回正就自动走回 .card 那 0.45s 的缓动，像水面弹回来 */
.card.is-tilting {
  transition: transform 0.12s ease-out, box-shadow 0.45s ease, background 0.45s ease;
  will-change: transform;
}

/* ---- 首页卡片进场 / 退场 ----
   只有首页那四张 .dash-grid .widget-card 参与，状态类由 JS 挂（data-reveal 记同一份状态）：
     .is-out     → 隐身：卡片整体停在画面底边之外（下坠距离 --reveal-from 由 JS 按当前视口算好覆盖）
     .is-in      → 进场：从画面外飞上来，落到原位的那一帧才开始抖动
     .is-jumping → 退场：先向上 ease-out 跳 --exit-hop，再 ease-in 掉到画面之外
   参数：--reveal-dur（进场总时长，飞入 0→62%、抖动 62→100%）
        --exit-dur（退场总时长，起跳 0→40%、坠落 40→100%）、--exit-hop（起跳高度）
        —— 这三个统一在 site-config.js 的「首页卡片 进场 / 退场动画」里改，别在这改。
        --reveal-from 由 JS 算（卡片顶端刚好落到画面底边外的下坠距离），CSS 里那份只是兜底。
   下面 var(--xxx, 兜底值) 括号里那份，是 site-config.js 万一没加载时的备份。
   落位抖动的幅度没做成变量，想调就动 card-fly-in 里 62% 之后那几个值。
   关键帧直接动 transform / opacity，不再动自定义属性：这两个属性所有浏览器都能逐帧平滑插值；
   而 --card-in-* 这类自定义属性只有支持 @property 的新浏览器才插值，老浏览器会整段跳变甚至不动，
   结果就只剩"原地淡入" —— 这正是上一版看着不像"飞入"的原因。
   每段都写全同一个 transform 形状（translate3d + rotate / scale），插值才不会退化成矩阵近似。 */
.dash-grid .widget-card {
  --reveal-from: 340px;            /* 兜底值：实际由 JS 按「当前视口高 − 卡片位置 + 缝」覆盖 */
  /* 注意：--reveal-dur / --exit-dur / --exit-hop 不在这里声明。
     site-config.js 把值写在 <html> 上，而元素自己声明的变量会盖掉继承来的值，
     所以这三个只在下面使用处写 var(--xxx, 兜底值)，值统一进 site-config.js 改。 */
}
.dash-grid .widget-card.is-out {
  transform: translate3d(0, var(--reveal-from), 0);
  opacity: 0;
  pointer-events: none;            /* 隐身时别接住点击、也别触发倾斜 */
}
.dash-grid .widget-card.is-in {
  animation: card-fly-in var(--reveal-dur, 0.85s) linear both;
  will-change: transform, opacity;
}
.dash-grid .widget-card.is-jumping {
  animation: card-jump-out var(--exit-dur, 0.6s) both;
  will-change: transform, opacity;
}
@keyframes card-fly-in {
  /* 0 → 62%：整块卡片从画面底边之下升上来。淡入压在前 5%（这时卡片还在画面外），
     所以全程看到的是"一块卡片往上飞"，不是原地渐变；淡入越早走完，位移越看得见 */
  0%   { opacity: 0; transform: translate3d(0, var(--reveal-from), 0) rotate(0deg);
         animation-timing-function: linear; }
  5%   { opacity: 1; }
  62%  { opacity: 1; transform: translate3d(0, 0, 0) rotate(0deg);
         animation-timing-function: linear; }        /* ← 这一帧＝抵达原位，抖动从这里开始 */
  /* 62 → 100%：整体抖动（上下 + 左右 + 摆角），振幅递减到静止 */
  66%  { transform: translate3d(1.6px, -5px, 0) rotate(0.7deg); }
  72%  { transform: translate3d(-1.3px, 3.4px, 0) rotate(-0.55deg); }
  78%  { transform: translate3d(0.8px, -2.1px, 0) rotate(0.36deg); }
  84%  { transform: translate3d(-0.4px, 1.1px, 0) rotate(-0.18deg); }
  90%  { transform: translate3d(0.15px, -0.4px, 0) rotate(0.06deg); }
  100% { opacity: 1; transform: translate3d(0, 0, 0) rotate(0deg); }
}
@keyframes card-jump-out {
  /* 0 → 40%：ease-out 起跳（起步快、到顶缓），跳到 --exit-hop 的高度，顶点轻微拉伸 */
  0%   { opacity: 1; transform: translate3d(0, 0, 0) scale(1);
         animation-timing-function: cubic-bezier(0.16, 0.84, 0.3, 1); }
  40%  { opacity: 1; transform: translate3d(0, calc(-1 * var(--exit-hop, 100px)), 0) scale(1.015);
         animation-timing-function: cubic-bezier(0.55, 0, 0.85, 0.4); }   /* ease-in：越落越快 */
  /* 40 → 100%：加速掉回画面之外（40~80% 坠落，80~100% 顺手淡出，跟隐身态接上。
     正常早就飞出画面看不见了；只有在坠落途中被反向滚动打断时才会露出来，用来兜住那一下闪烁） */
  80%  { opacity: 1; }
  100% { opacity: 0; transform: translate3d(0, var(--reveal-from), 0) scale(1); }
}
/* 系统开了"减少动效"：不飞不跳不抖，卡片照常显示（JS 那边也会直接退出） */
@media (prefers-reduced-motion: reduce) {
  .dash-grid .widget-card.is-out {
    transform: none; opacity: 1; pointer-events: auto;
  }
  .dash-grid .widget-card.is-in,
  .dash-grid .widget-card.is-jumping { animation: none; }
}

/* ---------------- 首页仪表盘（日历 / 音乐 / 站点统计 / 内容分类） ---------------- */
.dash-grid {
  /* x（首页卡片基准值）和卡片间距都在 site-config.js 里改；:root 里还有一份兜底值 */
  display: grid;
  /* 第 1 列 = 小卡列（宽 x）；第 2 列 1fr = 大卡列，吃掉剩下的宽度 */
  grid-template-columns: var(--home-card-x) 1fr;
  grid-template-rows: calc(var(--home-card-x) / 2) auto auto auto;
  gap: var(--home-card-gap);
  align-items: stretch;
}
/* ===== 首页布局（2026-10-29 起）=====
   行 1     ：顶部一排四张 —— 个人简介 → 音乐 → 正在做 → 今日一览（.home-top，跨两列）
   行 2~4   ：左列容器（日历 → 分类 → 统计）｜右下容器（文章 → 照片，纵向两等分）
   合计关系 ：日历+J+分类+J+统计 = X；文章+照片+J = X；照片 = 文章 = (X-J)/2
   顶部四张的列定义 = x + 右侧三等分，所以卡片边界和下面两列严格对齐。 */

/* 左列容器：只装日历 / 分类 / 统计三张方形卡 */
.home-left {
  grid-column: 1;
  grid-row: 2 / 5;
  display: flex;
  flex-direction: column;
  gap: var(--home-card-gap);
  min-width: 0;
}
/* 音乐卡 2026-10-29 起并入 .home-top（原来写死「第 1 行第 1 列」的那条已删） */
/* 顶部一排四张：个人简介 → 音乐 → 正在做 → 今日一览
   跨满两列。2026-10-29 二修：四张卡长度比 2:1:1:1——
   个人简介横条占两份宽，音乐 / 正在做 / 今日各一份。
   （上一版写的是「x + 三等分」，个人卡被压成最窄的一张，已废弃） */
.home-top {
  grid-column: 1 / -1;
  grid-row: 1;
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1fr;
  gap: var(--home-card-gap);
  min-width: 0;
}
/* 右下容器：文章 + 照片，纵向两等分
   高度按公式锁死：左列 = x/2 + J + (3x + 2J) = 3.5x + 3J
                 右下容器 = 左列 - x/2 - J = 3x + 2J
                 两张卡各 (3x + J)/2
   不锁这一行的话，行高是 auto，容器会塌成内容高度，两列底部就不齐了 */
.home-right-bottom {
  grid-column: 2;
  grid-row: 2 / 5;
  display: grid;
  grid-template-rows: 1fr 1fr;
  gap: var(--home-card-gap);
  height: calc(3 * var(--home-card-x) + 2 * var(--home-card-gap));
  min-width: 0;
}
/* 容器里的卡吃掉整格宽度，不再锁死为 x */
.home-left > .card,
.home-top > .card,
.home-right-bottom > .card { width: 100%; min-width: 0; }

.widget-card { padding: 22px 24px 24px; }
/* 首页卡片都定了高度，多出来的空间交给内部可伸缩区（日历格 / 统计格）吃掉 */
.dash-grid .widget-card {
  display: flex;
  flex-direction: column;
}
.card-square { height: var(--home-card-x); }
/* 音乐卡：半高横条，高度锁死 x/2。播放列表 2026-10-19 起改成悬浮层，展开不再撑高卡片 */
.card-half {
  height: calc(var(--home-card-x) / 2);
  position: relative;
}

/* ---------- 个人简介卡（2026-10-19）---------- */
.dash-grid .card-profile {
  height: calc(var(--home-card-x) / 2);
  flex-direction: row;
  align-items: center;
  /* 2026-10-29 晚：头像右移 15px、正文再右移 15px（合计 30px）。
     做法是左边距 20 → 35、间距 16 → 31，位移交给布局而不是 transform，
     这样 body 实际可排宽度同步收 30px，不会溢出被卡片的 overflow:hidden 裁掉。 */
  gap: 31px;
  padding: 18px 20px 18px 35px;
  overflow: hidden;
}
.profile-avatar {
  /* 2026-10-28：直径在 2026-10-25 版（0.45x）的基础上缩到 0.8 倍 = 0.36x；
     悬停整张个人卡时再放大 1.2 倍（scale 独立属性，0.2s ease-out，同上音乐封面）。 */
  width: calc(var(--home-card-x) * 0.36);
  height: calc(var(--home-card-x) * 0.36);
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 22px;
  font-weight: 700;
  color: var(--accent, #333);
  background: var(--accent-soft, rgba(0, 0, 0, .06));
  background-size: cover;
  background-position: center;
  scale: 1;
  transition: scale 0.2s ease-out;
}
@media (hover: hover) and (pointer: fine) {
  .card-profile:hover .profile-avatar { scale: 1.2; }
}
.profile-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.profile-name {
  font-size: 17px;
  font-weight: 700;
  line-height: 1.2;
}
.profile-bio {
  font-size: 12.5px;
  line-height: 1.45;
  opacity: .62;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.profile-links {
  display: flex;
  gap: 10px;
  margin-top: 3px;
}
.profile-links a {
  width: 18px;
  height: 18px;
  opacity: .5;
  transition: opacity .2s;
}
.profile-links a:hover { opacity: 1; }
.profile-links svg { display: block; width: 100%; height: 100%; }

/* ---------- Now 卡（2026-10-29：改成「标题 + 主文」的标准结构）----------
   原来左上角是绝对定位的 .now-label 小字，现在统一成 .widget-head + .widget-title，
   好跟另外四张卡同一套标题字号。 */
.card-now {
  height: calc(var(--home-card-x) / 2);
  padding: 14px 20px 16px;
  text-align: center;
}
.card-now .widget-head {
  justify-content: flex-start;
  margin-bottom: 0;
}
.card-now .now-main {
  margin: auto 0;                 /* 上下自动外边距 = 在标题和卡底之间垂直居中 */
  font-size: clamp(17px, 1.5vw, 22px);
  font-weight: 700;
  line-height: 1.3;
}

/* ---------- 今日一览（2026-10-29）----------
   复刻 yaronluo.com 左上角「今日手账」里的三样：当前日期时间、今年已走过多少天、年度进度条。
   语录没要；「今日 xx%」原本没要，2026-10-29 晚加在条右下角，字号比右上角年度百分比小一号。 */
.card-today {
  height: calc(var(--home-card-x) / 2);
  /* 2026-10-29 晚三次调整：内容改成贴底排（.today-body 用 flex-end），
     底边这一圈就是「最下面一行文字到卡底」的真实距离，直接给 15px。
     要微调只改这个末位数字：15 小一点文字更贴底，大一点更靠上。 */
  padding: 14px 20px 15px;
  /* container-type：让卡内时钟按卡片内容宽收（日期带上年份后一行要省） */
  container-type: inline-size;
}
/* 卡内纵向间距统一收到原来的 0.8 倍（12 / 10 / 7 / 2 各自 ×0.8） */
.card-today > .widget-head { margin-bottom: 9.6px; }
.today-body {
  display: flex;
  flex-direction: column;
  /* 2026-10-29 晚：center → flex-end，让整块贴着底部 padding 排，
     底部间距就只由 .card-today 的 padding-bottom 决定，好算也好调 */
  justify-content: flex-end;
  gap: -10px;
  flex: 1 1 auto;
  min-height: 0;
}
.today-top {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 6px;   /* 2026-10-29：日期带上年份后横向要更省，间距 10 → 6 */
}
.today-clock {
  display: flex;
  align-items: baseline;
  font-variant-numeric: tabular-nums;
}
.today-clock span {
  /* 2026-10-29：日期带上年份后这一行变挤，时钟按卡片内容宽收，宽卡仍是 30px */
  font-size: min(30px, 13.5cqw);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.5px;
}
.today-clock small {
  font-size: min(13px, 6cqw);
  font-weight: 600;
  opacity: .42;
}
.today-date {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1.6px;
  font-size: 11.5px;
  line-height: 1.25;
  color: var(--text-mute);
  white-space: nowrap;
}
/* 2026-10-29：星期比日期大 2px 并加粗，日期改成「2026 年 10 月 29 日」 */
.today-date > span:first-child {
  font-size: 13.5px;
  font-weight: 700;
}
.today-progress {
  display: flex;
  flex-direction: column;
  gap: 5.6px;
}
.today-progress-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-size: 11.5px;
  color: var(--text-mute);
}
.today-label b {
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.today-percent {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
/* 2026-10-29 晚：条下方一行，「今年还剩 x 天」左、「今日 x%」右。
   左字与「2026 年已走过」同为 11.5px；右比右上角 12.5px 小一号到 12px 且不加粗 */
.today-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-size: 11.5px;
  line-height: 1;          /* 行框收干净，padding-bottom 给多少就是多少 */
  color: var(--text-mute);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* 右下「今日 x%」：字号与右上角 12.5px 相同，颜色和粗细跟上面「2026 年已走过」一致（不加深、不加粗） */
/* 右下「今日 59.7%」：2026-10-29 晚二次调整，字号与左边「今年还剩 x 天」完全一致（11.5px），
   颜色/粗细仍跟「2026 年已走过」一样保持克制，不加粗 */
.today-daypercent {
  font-size: 11.5px;
  font-weight: 400;
}
.today-bar {
  --today-bar-h: 6px;              /* 条高，圆点直径 = 它 × 1.1，只改这一个就够 */
  position: relative;
  height: var(--today-bar-h);
  border-radius: 999px;
  background: rgba(120, 116, 130, .18);
  overflow: visible;               /* 圆点要比条高一点，得让它溢出 */
}
.today-bar i {
  position: relative;
  display: block;
  width: 0;
  height: 100%;
  border-radius: 999px;
  background: #3a3a43;             /* 2026-10-29：进度由 accent 改为深灰 */
  transition: width .8s cubic-bezier(.22, 1, .36, 1);
}
/* 白色小圆标出当前进度位置，直径 = 条高 × 1.1，跟着 i 的宽度一起过渡 */
.today-bar i::after {
  content: '';
  position: absolute;
  right: 0;
  top: 50%;
  width: calc(var(--today-bar-h) * 1.1);
  height: calc(var(--today-bar-h) * 1.1);
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 1px rgba(58, 58, 67, .55);   /* 细深灰描边：白点溢到浅色轨道那半边也能看见 */
  transform: translate(50%, -50%);
}

/* 标题字号（2026-10-29）：这五张卡的标题统一放大到日历标题的 1.3 倍，13 × 1.3 ≈ 17 */
.card-cal > .widget-head .widget-title,
.card-cat > .widget-head .widget-title,
.card-stat > .widget-head .widget-title,
.card-music > .widget-head .widget-title,
.card-now > .widget-head .widget-title,
.card-today > .widget-head .widget-title {
  font-size: 17px;
  letter-spacing: 1.1px;
}

/* ---------- 文章预览卡（2026-10-19）----------
   内间距 20px：内容区宽 w = 卡宽 - 40，预览占 3/4 w、信息占 1/4 w。
   「最近文章」标题不再占纵向空间，改浮在右上角、跟信息栏对齐，
   这样预览高度就能直接等于「卡高 - 40px」。
   .art-track 必须自己裁：否则下一片从右边 100% 处溢出，
   会露进右侧那 20px 内边距里（老大看到的「边缘」就是这个）。 */
.card-articles {
  position: relative;
  overflow: hidden;
  padding: 20px;
}
.card-articles .widget-head {
  position: absolute;
  top: 20px;
  right: 20px;
  width: calc(25% - 10px);
  margin: 0;
  z-index: 3;
  pointer-events: none;
}
/* 2026-10-19 三次改：位移不再用 translateX(-100%)，改成原生横向滚动 +
   scroll-snap，位移量由 JS 按 clientWidth 的像素算，跟 CSS 百分比无关，
   不会再出现「第二片跑偏 / 切过去看不见」的情况 */
.art-track {
  flex: 1;
  display: flex;
  min-height: 0;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.art-track::-webkit-scrollbar { display: none; }
/* 每一片：预览吃掉剩余宽度、信息约 1/4，中间留 20px 间距（2026-10-19 三次改） */
.art-slide {
  flex: 0 0 100%;
  min-width: 0;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: flex;
  flex-direction: row;
  align-items: stretch;
  justify-content: space-between;
  gap: 20px;
  padding: 0;
  cursor: pointer;        /* 2026-10-29：整片可点，点了跳到文章页打开这一篇 */
}
.art-slide-shot {
  flex: 1 1 0;
  min-width: 0;
  align-self: stretch;
  /* 2026-10-29 五改：纸面恒定在未缩小的尺寸，悬停不再动它。
     之前 0.88 → 1 是整块纸在跳，白纸一放大就看不出文字有没有反应，
     现在纸面固定，悬停效果全部交给纸上的文字。 */
}
/* 悬停效果：只给预览里的文字。
   压到哪一段，那一段就微微放大、颜色变深；白纸本身不缩放、不位移。
   scale 不占布局，不会把行挤开；origin 靠左，往右长一点，读起来像被点到那一行。 */
@media (hover: hover) and (pointer: fine) {
  .card-articles .art-shot-inner > *,
  .card-articles .art-slide-body > * {
    transition: color .18s ease, transform .18s ease;
    transform-origin: left center;
  }
  .card-articles .art-shot-inner > *:hover,
  .card-articles .art-slide-body > *:hover {
    color: #111;
    transform: scale(1.02);
  }
}
.art-slide-shot .art-paper {
  height: 100%;
  padding: 18px 20px;
}
/* 纸面很宽，正文限一档行宽，别让一行排到九十来个字；
   浅色正文在白纸上偏淡，首页预览这里单独压深一点 */
.art-slide-shot .art-shot-inner {
  max-width: 700px;
  margin: 0 auto;
  color: #5a5a5a;
}
.art-slide-shot .art-shot-inner h1,
.art-slide-shot .art-shot-inner h2,
.art-slide-shot .art-shot-inner h3 { color: #1f1f1f; }
.art-slide-body {
  flex: 0 0 calc(25% - 15px);
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
  padding: 20px 6px 56px 18px;   /* 标题挪走后上留白收到 20，下留切换按钮 */
}
.art-slide-date {
  font-size: 13.5px;
  letter-spacing: .04em;
  opacity: .45;
}
.art-slide-title {
  font-size: 19px;
  font-weight: 700;
  line-height: 1.35;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
}
.art-slide-summary {
  font-size: 14.5px;
  line-height: 1.65;
  opacity: .6;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
}
.art-slide-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.art-slide-tags span {
  font-size: 13px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--accent-soft, rgba(127, 127, 127, .12));
  opacity: .8;
}
.art-empty { font-size: 13px; opacity: .45; margin: auto 0; }
/* 卡片右下角的切换按钮（2026-10-19：跟着信息栏走，不再压住预览纸面） */
.art-nav {
  position: absolute;
  bottom: 20px;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 50%;
  font-size: 18px;
  line-height: 1;
  color: inherit;
  background: rgba(127, 127, 127, .12);
  cursor: pointer;
  opacity: .5;
  z-index: 2;
  transition: opacity .2s, background .2s;
}
.art-nav:hover { opacity: 1; background: rgba(127, 127, 127, .22); }
.art-nav-prev { right: 60px; }
.art-nav-next { right: 20px; }

/* ---------- 相册预览卡（2026-10-19）---------- */
.card-photos {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  padding: 20px;   /* 内间距 20px，图不贴卡片边 */
}
.photo-track {
  display: flex;
  flex: 1;
  min-height: 0;
  /* 2026-10-19 三次改：位移改用原生横向滚动（像素法），
     不再依赖 translateX 的百分比，裁切与切换都更稳 */
  overflow-x: auto;
  overflow-y: hidden;
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;   /* 2026-10-19 四次改：像素法会有亚像素误差，
                                      下一张在右边缘露出细边，交给 snap 精确停靠 */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.photo-track::-webkit-scrollbar { display: none; }
.photo-slide {
  position: relative;
  flex: 0 0 100%;
  height: 100%;
  overflow: hidden;
  border-radius: var(--radius, 24px);   /* 裁切框跟卡片同圆角 */
  scroll-snap-align: start;
  scroll-snap-stop: always;
  background: rgba(127, 127, 127, .08);   /* 图没到位前先占个底色，别是一块黑 */
}
.photo-slide img:not([src]) { opacity: 0; }   /* 还没轮到挂 src 时别闪 alt 文字 */
.photo-slide img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.photo-slide-cap {
  position: absolute;
  left: 16px;
  bottom: 14px;
  font-size: 12px;
  color: #fff;
  text-shadow: 0 1px 6px rgba(0, 0, 0, .55);
}
/* 音乐卡专属规则：2026-10-14 起认它自己的 .card-music 类，
   不再靠 .dash-col-right 前缀定位（它已从右列挪到左列最上，老前缀不再命中） */
.card-music {
  padding: 14px 20px 16px;
  justify-content: flex-start;
  /* 2026-10-29：顶部改成 2:1:1:1 后音乐卡只剩一份宽（约 247px），
     卡内元素一律按卡片内容宽「等比」缩放，比例与换位置之前完全一致，
     只是整体小一号；原始 px 值是宽卡（内容宽 260）下的上限 */
  container-type: inline-size;
}
.widget-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}
.widget-title {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 1.4px;
  color: var(--text-mute);
}

/* 日历 */
.cal-nav { display: flex; align-items: center; gap: 6px; }
.cal-btn {
  width: 26px; height: 26px;
  border-radius: 9px;
  font-size: 15px;
  line-height: 1;
  color: var(--text-soft);
  background: rgba(255, 255, 255, 0.5);
  transition: background 0.25s ease, color 0.25s ease;
}
.cal-btn:hover { background: rgba(224, 90, 82, 0.12); color: var(--accent); }
.cal-back { width: auto; padding: 0 10px; font-size: 12px; }
/* 2026-10-29：年月 / 日期提醒跟日期数字左对齐。
   算式：cal-body 内容宽 W，7 列 + 6 个 2px 缝 → 列宽 c = (W − 12) / 7；
   每个日期格里的数字是居中的，13px 字号单个数字宽约 7.2px，
   所以第一列数字的左缘离 body 左边 = (c − 7.2) / 2 = (W − 12) / 14 − 3.6px。
   （两位数会比这条线再左出 3.6px，居中对齐天然如此；要贴齐两位数就把 −3.6px 改掉） */
.cal-month, .cal-count {
  padding-left: calc((100% - 12px) / 14 - 3.6px);
}
.cal-month { margin: 0 0 10px; font-size: 18px; font-weight: 600; letter-spacing: -0.3px; }
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  grid-template-rows: auto;          /* 第一行是星期表头 */
  grid-auto-rows: minmax(0, 1fr);    /* 周行平分剩下的高度 */
  gap: 2px;
  flex: 1 1 auto;
  min-height: 0;
}
/* 2026-10-29：年月 / 日期格 / 底部提示整块缩放到 0.88（上一版 0.8 的 1.1 倍），
   悬停整张日历卡放大到当前尺寸的 1.15 倍 = 1.012，0.2s ease-out，移开同样时长还原。
   三部分共用一个 wrapper 一起缩，间距按同一比例走，不会各自在原地缩放。 */
.cal-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  scale: 0.88;
  transition: scale 0.2s ease-out;
}
@media (hover: hover) and (pointer: fine) {
  .card-cal:hover .cal-body { scale: 1.012; }
}
.cal-dow {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2px 0 4px;
  text-align: center;
  font-size: 11px;
  color: var(--text-mute);
}
.cal-day {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  font-size: 13px;
  color: var(--text-soft);
}
.cal-day.is-today {
  background: linear-gradient(135deg, var(--accent), #ff9a7a);
  color: #fff;
  font-weight: 600;
  box-shadow: 0 6px 16px rgba(224, 90, 82, 0.3);
}
.cal-day.is-holiday::after {
  content: "";
  position: absolute;
  left: 50%; bottom: 3px;
  width: 4px; height: 4px;
  margin-left: -2px;
  border-radius: 50%;
  background: var(--accent-soft);
}
.cal-day.is-today.is-holiday::after { background: #fff; }
.cal-count { margin: 12px 0 0; font-size: 12.5px; color: var(--text-soft); }
.cal-count b { color: var(--accent); font-size: 14px; }

/* 音乐卡：左边旋转封面，右边上下两行（歌名 / 控制键） */
.music-row {
  display: flex;
  align-items: center;
  gap: 14px;
  flex: 1 1 auto;         /* 吃掉标题以外的高度 */
  min-height: 0;
}
.music-cover {
  flex: 0 0 auto;
  align-self: center;     /* 居中就好，别再用行高撑——卡片高度改成 min-height 后那样算会把封面撑爆 */
  width: calc(var(--home-card-x) * 0.30);
  height: calc(var(--home-card-x) * 0.30);
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  overflow: hidden;
  box-shadow: 0 8px 22px rgba(48, 42, 60, 0.2);
  animation: spin 22s linear infinite;
  animation-play-state: paused;
}
.music-cover.is-playing { animation-play-state: running; }
.music-cover img { width: 100%; height: 100%; object-fit: cover; }
/* 2026-10-29：首页这张封面能点，点一下播放/暂停，给个手型提示 */
#homeCover { cursor: pointer; -webkit-tap-highlight-color: transparent; }

/* 2026-10-28：鼠标悬停音乐卡时圆封面放大到 1.2 倍，移开还原，都是 ease-out 0.5s。
   这里用独立的 scale 属性，不写 transform——封面自己的 transform 被 spin 旋转动画占用，
   两者同时用会互相覆盖；scale 与 transform 是分开合成的，互不干扰。 */
.music-cover { scale: 1; transition: scale 0.2s ease-out; }
@media (hover: hover) and (pointer: fine) {
  .card-music:hover .music-cover { scale: 1.2; }
}

/* 没有内嵌封面的歌：画一张黑胶唱片顶上（跟着 is-playing 一起转） */
.music-cover.no-cover,
.player-cover.no-cover {
  position: relative;
  background:
    radial-gradient(circle at 50% 50%,
      #45454e 0 23%,
      #16161a 23% 25%,
      #0e0e11 25% 100%),
    repeating-radial-gradient(circle at 50% 50%,
      rgba(255, 255, 255, 0.045) 0 1px,
      rgba(0, 0, 0, 0) 1px 4px);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06),
              0 12px 30px rgba(20, 18, 26, 0.32);
}
.music-cover.no-cover::after,
.player-cover.no-cover::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 7px; height: 7px;
  margin: -3.5px 0 0 -3.5px;
  border-radius: 50%;
  background: #0a0a0c;
}
.music-cover.no-cover img,
.player-cover.no-cover img { display: none; }
/* 右侧竖排：上面歌名歌手、下面一排控制键；长了就省略号，不撑破卡片 */
.music-side {
  flex: 1 1 auto;
  min-width: 0;
  align-self: stretch;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 8px;
}
/* 2026-10-28：右侧（歌名 / 歌手 / 5 个控制键）整体右移一个歌名字的距离，
   给悬停放大后的封面让出位置。歌名是 14px，所以这里取 14px；想微调只改这一个值。 */
.card-music .music-side { padding-left: 6px; }
.music-meta { min-width: 0; }
.music-meta strong {
  display: block;
  margin-bottom: 3px;
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.music-meta small {
  display: block;
  color: var(--text-mute);
  font-size: 11.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.music-controls {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
}
/* 半高卡里五个键要挤在一行：播放键比其余四个大一号，其余四个一样大。
   数值都摆在这儿，想调大小直接改这两行；换行兜底是防窄屏挤爆 */
.card-music .ctrl-main { width: 34px; height: 34px; font-size: 15px; }
.card-music .ctrl-sm { width: 25px; height: 25px; font-size: 10px; }
.card-music .music-controls { gap: 3px; flex-wrap: wrap; }
.card-music .ctrl-sm .ic { width: 1.5em; height: 1.5em; }

/* 2026-10-25：首页音乐卡只有半张卡宽，音量滑块展开时不再占位（不然 5 个键会被挤成两行），
   改成浮在控件行上方：白底胶囊 + 内嵌细轨道；5 秒不碰、点卡片外自动收起，逻辑在 app.js */
.card-music .vol-wrap { position: relative; }
.card-music .vol-range {
  position: absolute;
  right: 0;
  bottom: calc(100% + 10px);
  width: 104px;
  height: 26px;
  padding: 0 12px;
  background: #fff;
  box-shadow: 0 8px 20px rgba(40, 34, 50, 0.22);
  z-index: 6;
}
.card-music .vol-range::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 999px;
  background: rgba(120, 116, 130, 0.28);
}
/* 指针纵向对齐：Chromium 在 input 被拉高（26px 胶囊）后不再把 13px 圆点居中到 4px 轨道，
   会贴着轨道上沿往下排，看着偏下 ≈ (13-4)/2，所以手动上提 4.5px */
.card-music .vol-range::-webkit-slider-thumb {
  margin-top: -4.5px;
}
.card-music .vol-range::-moz-range-track {
  height: 4px;
  border-radius: 999px;
  background: rgba(120, 116, 130, 0.28);
}
.ctrl-main { width: 48px; height: 48px; font-size: 17px; }
.ctrl-sm { width: 38px; height: 38px; font-size: 13px; }

/* ---- 按钮图标 ----
   全部用 Material Symbols 的内联 SVG（Apache-2.0），不引外部字体，国内访问也不卡。
   尺寸写成 em，跟着按钮自己的 font-size 走，所以下面三行就够了 */
.ic { width: 1.2em; height: 1.2em; display: block; }
.ctrl .ic { width: 1.35em; height: 1.35em; }
/* 播放/暂停（两个图标）、循环键（四个图标：顺序 / 单曲 / 随机 / 不循环）：同一个按钮里塞多个图标，按状态显隐 */
.ic-pause-rounded, .ic-repeat-one-rounded, .ic-shuffle-rounded, .ic-repeat-off, .ic-volume-off { display: none; }
.is-playing .ic-play-arrow-rounded { display: none; }
.is-playing .ic-pause-rounded { display: block; }
/* 循环键一个按钮里塞了四个图标（顺序 / 单曲 / 随机 / 不循环），按状态显隐 */
.is-single .ic-repeat-rounded { display: none; }
.is-single .ic-repeat-one-rounded { display: block; }
.is-shuffle .ic-repeat-rounded { display: none; }
.is-shuffle .ic-shuffle-rounded { display: block; }
.is-off .ic-repeat-rounded { display: none; }
.is-off .ic-repeat-off { display: block; }
/* 音量键：拖到 0（或点成静音）时换成划掉的图标，判定在 app.js 的 paintVolume 里 */
.is-muted .ic-volume-up { display: none; }
.is-muted .ic-volume-off { display: block; }
/* 不循环：整个键变灰，一眼看出「现在没在循环」 */
.ctrl.is-off { background: rgba(120, 116, 130, 0.2); box-shadow: none; color: var(--text-mute); }
/* 标题右边那个列表按钮：透明底，比圆形主按钮小一号 */
.ctrl-ghost {
  width: 26px; height: 26px;
  background: transparent;
  box-shadow: none;
  color: var(--text-mute);
  font-size: 13px;
}
.ctrl-ghost:hover { transform: scale(1.12); color: var(--accent); }
.ctrl-ghost.is-open { color: var(--accent); }

/* ---- 首页音乐卡的播放列表抽屉 ----
   默认 0 行高、点开后展开。用 grid-template-rows 0fr → 1fr 是唯一
   能「按内容高度」平滑展开、又不用写死像素高度的做法 */
/* 2026-10-19：播放列表改成卡片下方的悬浮层。
   卡片高度锁死 x/2 之后，抽屉若还留在文档流里会把卡片撑变形，所以改成 absolute 浮在下面。
   展开动画仍是 grid-template-rows 0fr→1fr（内层高度上限的注意事项见下一条注释）。 */
.music-drawer {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 40;
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  margin-top: 8px;
  border-radius: var(--radius, 18px);
  background: var(--glass, #fff);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  backdrop-filter: blur(18px) saturate(1.3);
  box-shadow: 0 18px 40px rgba(0, 0, 0, .16);
  overflow: hidden;
  pointer-events: none;
  transition: grid-template-rows 0.34s cubic-bezier(0.4, 0, 0.2, 1),
              opacity 0.28s ease,
              margin-top 0.34s cubic-bezier(0.4, 0, 0.2, 1);
}
.music-drawer.is-open {
  grid-template-rows: 1fr;
  opacity: 1;
  pointer-events: auto;
}
/* 内层始终是「有上限的滚动容器」：max-height 和 overflow-y 不能跟着
   is-open 一起开关。跟着开关的话，收回那一帧的高度基准会从 176px
   变成整串列表的真实高度，grid 收拢动画就被内容撑长一截再缩回去，
   看着就是卡片先被拉长再弹回原状 */
.music-drawer-inner {
  min-height: 0;
  max-height: 176px;
  overflow-x: hidden;
  overflow-y: auto;
}
/* 抽屉里的列表比音乐页紧凑一点：卡片本来就小 */
.track-list-home { gap: 2px; }
.track-list-home .track-item { padding: 5px 8px; border-radius: 10px; }
.track-list-home .track-meta strong { font-size: 12.5px; }
.track-list-home .track-meta small { font-size: 11px; }

/* 站点统计（正方形 x；行与行之间不再有独立方块，纵向排列） */
/* 2026-10-29：「已运行」整行加粗（加粗只给这行和数值） */
.stat-uptime { margin: 0 0 12px; font-size: 12.5px; font-weight: 700; color: var(--text-soft); }
.stat-uptime b { color: var(--accent); font-size: 15px; font-weight: 700; }

/* 卡内信息整体缩放（2026-10-29 第四版）
   默认 scale(0.88)：内容宽 238 → 209，与日历卡 .cal-grid 的内容宽对齐，
   原点在卡内容区中心（老大要内容在卡里居中，不是贴左上）。
   悬停：卡片自身照日历卡那套放大到 1.05，内容同时从 0.88 长到 0.95，
   相乘约等于 1.0 —— 也就是悬停时内容正好长满卡内宽，看得见但不夸张。 */
.stat-body,
.cat-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  transform: scale(0.88);
  transform-origin: center center;
  transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}
.card-stat:hover,
.card-cat:hover { --card-scale: 1.05; }
.card-stat:hover .stat-body,
.card-cat:hover .cat-body { transform: scale(0.95); }
.stat-grid {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;   /* 2026-10-29：不再用 space-between 撑满整高，行距交给 gap */
  gap: 16px;                     /* 缩放后视觉 16×0.88 ≈ 14px（原来的 0.6 倍行距） */
  flex: 1 1 auto;
  min-height: 0;
}
.stat-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;   /* 名称在左、数值贴右 */
  gap: 10px;
  min-height: 0;
}
/* 名称：2026-10-29 取消加粗，加粗只留给「已运行」那行和数值 */
.stat-item span { font-size: 14px; font-weight: 400; color: var(--text); }
.stat-item strong { font-size: 14px; font-weight: 600; color: var(--text-soft); }

/* 内容分类 */
.cat-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.cat-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 9px 13px;
  border-radius: 13px;
  background: rgba(255, 255, 255, 0.42);
  font-size: 13.5px;
  transition: background 0.25s ease;
}
.cat-item:hover { background: var(--glass-strong); }
.cat-item em { font-style: normal; font-size: 13px; font-weight: 600; color: var(--accent); }
/* 标签行贴着卡片底部，把方形卡下方的空档撑住 */
.tag-row { display: flex; flex-wrap: wrap; gap: 7px; margin-top: auto; padding-top: 14px; }

/* 窄屏：两列并一列，卡片不再锁死高度，改回按内容自适应 */
@media (max-width: 720px) {
  /* 窄屏（2026-10-19 改）：三个容器并成一列，
     顺序「音乐 → 日历 → 分类 → 统计 → 个人 → Now → 文章 → 照片」用 order 重排，
     桌面版那套 grid-row 在这里全部失效。 */
  .dash-grid {
    grid-template-columns: 1fr;
    grid-template-rows: none;
  }
  /* 窄屏（2026-10-29 改）：顶部四张卡并进单列，顺序
     个人简介 → 音乐 → 正在做 → 今日一览 → 日历 → 分类 → 统计 → 文章 → 照片 */
  .home-top, .home-left, .home-right-bottom {
    grid-column: 1;
    grid-row: auto;
  }
  .home-top {
    grid-template-columns: 1fr;
    order: 1;
  }
  .home-left { order: 2; }
  .home-right-bottom { order: 3; }

  .home-left, .home-right-bottom {
    display: flex;
    flex-direction: column;
    gap: var(--home-card-gap);
    height: auto;          /* 单列手机布局下别再锁 3x+2J，交给内容撑 */
  }
  .dash-grid .widget-card { width: 100%; }
  .card-square { height: auto; }
  /* 窄屏下几张固定高度的卡改回按内容自适应 */
  .dash-grid .card-profile, .card-now, .card-today, .card-half { height: auto; }
  .card-profile { min-height: 96px; }
  /* 2026-10-29 晚：窄屏卡片太窄，桌面那套右移（头像 +15 / 正文 +30）会把正文挤成细条，这里退回原值 */
  .dash-grid .card-profile { padding: 18px 20px; gap: 16px; }
  /* 窄屏卡片高度改成按内容自适应，百分比就没有参照了，给头像一个固定值（86 × 0.8 = 69） */
  .card-profile .profile-avatar { width: 69px; height: 69px; }
  .card-now { min-height: 120px; }
  .card-today { min-height: 130px; }
  .card-articles { min-height: 250px; padding: 10px; }
  /* 窄屏文章片：3/4 与 1/4 横排放不下，改成纸片在上、信息在下 */
  .card-articles .widget-head {
    position: static;
    width: auto;
    margin: 0 0 6px 2px;
    pointer-events: auto;
  }
  .art-slide { flex-direction: column; gap: 8px; }
  /* 窄屏卡片高度按内容走，这里给轨道一个定高，纸片才分得到剩余高度 */
  .art-track { flex: 0 0 auto; height: 210px; }
  .art-slide-shot { flex: 1 1 0; min-height: 0; width: 100%; }
  .art-slide-shot .art-paper { padding: 10px 12px; }
  .art-slide-shot .art-shot-inner { font-size: 11px; line-height: 1.6; max-width: none; }
  .art-slide-shot .art-shot-inner h1 { font-size: 13px; }
  .art-slide-shot .art-shot-inner h2,
  .art-slide-shot .art-shot-inner h3 { font-size: 12px; }
  .art-slide-body {
    flex: 0 0 auto;
    width: 100%;
    padding: 0 2px 38px 2px;
    gap: 4px;
  }
  .art-slide-title { font-size: 17px; -webkit-line-clamp: 2; }
  .art-slide-summary, .art-slide-tags { display: none; }
  .art-nav { bottom: 8px; }
  .art-nav-prev { right: 42px; }
  .art-nav-next { right: 8px; }
  .card-photos { min-height: 200px; padding: 10px; }
  .cal-day { min-height: 30px; }
  .stat-item { padding: 2px 0; }
}

/* ---------------- 项目 ---------------- */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 20px;
}
.project-card { overflow: hidden; display: flex; flex-direction: column; }
.project-thumb {
  height: 130px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 34px;
  background: linear-gradient(135deg, #ffe6e0, #fff0d8 60%, #e6ecff);
  color: #fff;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
}
.thumb-4399 { background: linear-gradient(135deg, #ffb3a7, #ffd08a); }
.project-body { padding: 20px 22px 22px; }
.project-body h3 { margin: 0 0 8px; font-size: 16px; }
.project-body p { margin: 0 0 14px; font-size: 13px; line-height: 1.7; color: var(--text-soft); }
.tag {
  display: inline-block;
  padding: 3px 11px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  color: var(--accent);
  background: rgba(224, 90, 82, 0.12);
}
.tag-soon { color: var(--text-mute); background: rgba(120, 116, 130, 0.12); }
.project-card.is-soon { opacity: 0.72; }
.project-card.is-soon:hover { --card-lift: 0px; --card-scale: 1; }   /* 开发中的卡片不浮起、不放大、也不倾斜（JS 用 :not(.is-soon) 挑掉） */

/* ---------------- 照片墙 ---------------- */
.album-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 20px;
}
/* PC（真有指针的设备）：相册文件夹卡固定一行 5 个、横向平均分布。
   2026-10-08 老大要求；手机保持上面的 auto-fill 矩阵，一点不动。
   判定条件跟 .album-card:hover 的悬停动效用的是同一条，所以"带动效的那套"和
   "排 5 个的那套"天然是同一个对象，不会各改各的。 */
/* PC 且窗口够宽：相册文件夹一排 4 个、间距 40px，卡片大小由这两条同比算出来
   （1650px 内容宽下每个约 382px）。窗口窄于 1001px 时不强排 4 个，
   交回上面的 auto-fill 按 250px 一列自适应，免得挤成小方块。
   2026-10-08 老大要求：由一排 5 个改成 4 个，间距 20px 改成 40px。 */
@media (hover: hover) and (pointer: fine) and (min-width: 1001px) {
  .album-grid { grid-template-columns: repeat(4, 1fr); gap: 40px; }
}
/* 相册卡不要卡片底：封面本身就是一沓照片，再套个毛玻璃圆角框会显脏。
   .card 的 hover 态（白色毛玻璃底 + 大投影）优先级比这里高，所以必须连同 :hover 一起关，
   否则鼠标一移上去白框又冒出来了。浮起和鼠标倾斜（--card-lift / --card-rx 那套）照旧保留。 */
.album-card,
.album-card:hover {
  padding: 0;
  cursor: pointer;
  background: none;
  border: none;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  /* 整沓照片的上浮距离：0 = 原位。悬停时被下面那条 :hover 规则改成负值 */
  --stack-lift: 0px;
  /* 悬停时照片会飞出卡片顶部，抬一层免得被上一行的卡片压住 */
  position: relative;
  z-index: 1;
}
/* 悬停：照片 / "省略"白矩形沿各自的倾斜方向往自己"上方"挪 --album-lift，
   时长走 --album-lift-dur（去程回程一样长）。两个值都在 site-config.js 里。
   只在真有指针的悬停设备上生效，触屏不受影响 */
@media (hover: hover) and (pointer: fine) {
  .album-card:hover {
    --stack-lift: calc(-1 * var(--album-lift, 50px));
    z-index: 3;
  }
}
/* 关了动效就让它瞬间到位，不要 1s 的漂移 */
@media (prefers-reduced-motion: reduce) {
  .album-stack > * { transition: none; }
}

/* 相册卡的封面：一沓照片，以"中间那张的底部中心"（也就是所有照片重合的底边中点）为轴心摊开。
   照片和垫底的"省略"白矩形贴在同一条底边中线上，只靠 rotate 分开。
   角度在 site-config.js：--album-tilt / --album-ghost-tilt / --album-ghost-tilt-step。

   上 / 右 / 下 / 左各留一块空白（百分比 padding 按容器宽算）：绕底边中点转开之后，
   顶上会拱出去、右下会探出去，不留空就会被隔壁卡片压住、或者顶到上下相邻的内容。
   数值按最大 22° 的包围盒估算（元素 4:3，宽 w、高 0.75w）：
     右上角横移 w/2·cos + 0.75w·sin ≈ 0.745w → 比右边缘多出 0.245w
     左上角竖移 w/2·sin + 0.75w·cos ≈ 0.883w → 比顶边多出 0.133w
     右下角下探 w/2·sin ≈ 0.187w，左下角左移 w/2·cos ≈ 0.464w（相对中点） */
.album-stack {
  position: relative;
  display: grid;
  padding: 11% 21% 15% 3%;
  /* 整沓照片投一层向右下的影子。drop-shadow 作用于整棵子树，所以是"一沓"的外轮廓影，
     而不是每张照片各投一层 */
  filter: drop-shadow(7px 9px 12px rgba(48, 42, 60, 0.26));
}
/* 所有照片 / 白矩形叠在同一格里，底边对齐 → 底边中点自然重合，再各自绕底边中点转。
   align-self 必须写 end，不然 grid 默认拉伸会把 aspect-ratio 顶掉 */
.album-stack > * {
  grid-area: 1 / 1;
  justify-self: start;
  align-self: end;
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-sm);
  /* 轴心 = 元素底边的中点；因为所有元素完全重叠，它就是"中间那张的底部中心" */
  transform-origin: center bottom;
  /* rotate 写在前面，translateY 就落进"各自旋转之后"的坐标系里 ——
     每一张都是沿着自己的倾斜方向往自己的"上方"挪，三张像扇面一样散开飞起。
     想换成三张一起竖直平移，把这两段调序成 translateY(...) rotate(...) 就行。
     升距 --stack-lift 平时是 0（原位），悬停时由 .album-card:hover 改成负值，
     数值来自 site-config.js 的 --album-lift（默认 50px）。 */
  transform: rotate(var(--rot, 0deg)) translateY(var(--stack-lift, 0px));
  transition: transform var(--album-lift-dur, 1s) ease;
}
/* 每张照片带一圈白色相框，像一张张冲印出来的纸相。
   粗细 = site-config.js 里的 --album-photo-border；垫底的白矩形不加，免得和照片糊成一片 */
.album-stack .stack-photo {
  overflow: hidden;
  background: rgba(255, 255, 255, 0.6);
  border: var(--album-photo-border, 2px) solid #fff;
}
.album-stack .stack-photo img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
.album-card:hover .stack-photo:last-child img { transform: scale(1.04); }
/* 半透明"省略"矩形：垫在照片堆最底下，比照片歪得多，从右下侧探出来表示"底下还有更多张" */
.album-stack .stack-ghost {
  background: rgba(255, 255, 255, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.78);
}
/* 照片堆下面那行文件夹名称：字号 = site-config.js 里的 --album-name-size。
   左右 margin 跟 .album-stack 的左右 padding 对齐（21% / 3%），
   这样名字居中时对的是照片堆的横向中心，而不是整张卡片的中心 */
.album-card h3 {
  margin: 10px 21% 0 3%;
  font-size: var(--album-name-size, 16px);
  text-align: center;
}

.back-link {
  margin-bottom: 18px;
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-soft);
  border-radius: 999px;
  background: var(--glass);
  border: 1px solid var(--glass-border);
}
.back-link:hover { color: var(--accent); }
/* 相册顶部工具行：返回相册 + 排列切换并排（2026-11-02） */
.album-tools {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 18px;
}
.album-tools .back-link { margin-bottom: 0; }

/* 桌面端横向胶片：当前照片居中放大，两侧小图依次排开（窄屏 / 触摸设备不显示，仍用 .photo-grid） */
.photo-film {
  position: relative;
  /* 按屏幕宽度铺开，不受 .wrap（最宽 var(--page-w)，默认 1650px）的内容宽度限制：
     100vw 是视口宽，左右各往外撑到视口边缘；多出来的部分由 body 的 overflow-x:hidden 收掉 */
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  /* 整条胶片带的高度：比上一版放大 1.5 倍，中间那张大图（高度 = 容器高 × 0.78）跟着一起放大；
     两侧小图的系数在 app.js 的 filmLayout 里另行折算，观感尺寸保持原样 */
  height: clamp(495px, 72vh, 810px);
  cursor: grab;
  -webkit-user-select: none;
  user-select: none;
  touch-action: pan-y;
}
.photo-film.is-dragging { cursor: grabbing; }
.film-stage {
  position: absolute;
  inset: 0;
  /* 两端淡出，做出"流进流出"的观感（用 % 而不是 vw：微信 WebView 的 vw 会按虚拟视口算，比容器宽好几倍） */
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%);
}
.film-item {
  position: absolute;
  top: 50%;
  left: 50%;
  height: 44%;
  width: auto;
  /* 全局 img{max-width:100%} 会在窄屏把大图压窄、破掉宽高比，这里必须放开 */
  max-width: none;
  border-radius: var(--radius-sm);
  object-fit: cover;
  box-shadow: var(--shadow);
  opacity: 0.5;
  filter: saturate(0.86);
  transform: translateX(-50%) translateY(-50%);
  transition: transform 0.55s cubic-bezier(0.22, 1, 0.36, 1),
              height 0.55s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 0.4s ease, filter 0.4s ease;
  will-change: transform, height;
}
.film-item.is-active {
  opacity: 1;
  filter: none;
  box-shadow: var(--shadow-lg);
}
.photo-film.is-dragging .film-item { transition: none; }
.film-caption {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  margin: 0;
  text-align: center;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-soft);
}

.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 14px;
}
.photo-item {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-sm);
  overflow: hidden;
  cursor: zoom-in;
  box-shadow: var(--shadow);
  border: 1px solid var(--glass-border);
}
.photo-item img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
.photo-item:hover img { transform: scale(1.02); }

/* 矩阵排列（2026-11-02）：一行四张、自上而下铺。
   单张宽度 = --photo-x（JS 按胶片两侧小图的口径算好写进来：小图宽 = 胶片带高 × 0.46）。
   四张连 gap 的总宽就是 --photo-x*4 + 3gap，容器更宽时整组居中而不是把图拉大；
   容器更窄时四列均分收窄，宁可比基准小一点也不横向溢出。
   照片是按 3:2 裁的，格子和胶片一致用 3/2，不再用相册总览的 4/3。 */
.photo-grid.is-matrix4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 20px;
  max-width: calc(var(--photo-x, 372px) * 4 + 80px);
  margin-left: auto;
  margin-right: auto;
}
.photo-grid.is-matrix4 .photo-item { aspect-ratio: 3 / 2; }
/* 悬停放大 1.2 倍、ease-out 0.5s，只在矩阵排列里开；
   胶片的小图有自己的位移 / 高度过渡，不能和这个混在一起 */
@media (hover: hover) and (pointer: fine) {
  .photo-grid.is-matrix4 .photo-item {
    transition: transform 0.2s ease-out;
  }
  .photo-grid.is-matrix4 .photo-item:hover {
    transform: scale(1.1);
    z-index: 3;
  }
  /* 总览的小图本来会在 hover 时放大内部 img，这里让位给格子本身的 1.2 倍 */
  .photo-grid.is-matrix4 .photo-item:hover img { transform: none; }
}
/* 手机上四列太挤，退成两列铺满（触摸设备只有矩阵这一种排列） */
@media (max-width: 640px) {
  .photo-grid.is-matrix4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    max-width: none;
  }
}
.photo-item span {
  position: absolute;
  left: 10px; bottom: 9px;
  font-size: 12px;
  font-weight: 600;
  color: #fff;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
}

/* ---------------- 图片放大 ---------------- */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(30, 26, 34, 0.72);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  animation: fadeIn 0.3s ease;
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.lightbox img {
  max-width: 88vw;
  max-height: 82vh;
  border-radius: 14px;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.45);
  animation: zoomIn 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes zoomIn { from { opacity: 0; transform: scale(0.94); } to { opacity: 1; transform: scale(1); } }
.lb-close, .lb-nav {
  position: absolute;
  color: #fff;
  font-size: 26px;
  line-height: 1;
  width: 46px; height: 46px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.16);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  transition: background 0.25s ease;
}
.lb-close:hover, .lb-nav:hover { background: rgba(255, 255, 255, 0.3); }
.lb-close { top: 24px; right: 24px; }
.lb-prev { left: 3vw; top: 50%; margin-top: -23px; }
.lb-next { right: 3vw; top: 50%; margin-top: -23px; }
.lb-caption {
  position: absolute;
  bottom: 26px; left: 0; right: 0;
  text-align: center;
  color: rgba(255, 255, 255, 0.9);
  font-size: 13px;
}

/* ---------------- 音乐 ---------------- */
.player-card {
  display: flex;
  gap: 26px;
  padding: 26px;
  align-items: center;
}
.player-cover {
  flex: 0 0 auto;
  width: 150px; height: 150px;
  border-radius: 50%;
  overflow: hidden;
  box-shadow: 0 12px 30px rgba(48, 42, 60, 0.22);
  animation: spin 22s linear infinite;
  animation-play-state: paused;
}
.player-cover.is-playing { animation-play-state: running; }
/* 音乐页旋转封面外面套一层：尺寸和「不跟着转」的右下阴影都挂外层（第三轮 #6）。
   尺寸由 app.js 的 sizeMusicCards() 在卡高里取最大方形。 */
.player-cover-wrap {
  position: relative;
  flex: 0 0 auto;
  width: 150px; height: 150px;
}
.player-cover-wrap .player-cover {
  position: relative;
  width: 100%; height: 100%;
  box-shadow: none;             /* 阴影挪到下面那层透明图案上，免得跟着唱片转 */
}
.player-cover-shadow {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: 14px 16px 34px rgba(48, 42, 60, 0.34);
  pointer-events: none;
}
.player-cover img { width: 100%; height: 100%; object-fit: cover; }
@keyframes spin { to { transform: rotate(360deg); } }

.player-main { flex: 1; min-width: 0; }
.player-main h3 { margin: 0 0 4px; font-size: 19px; }
.player-artist { margin: 0 0 18px; font-size: 13px; color: var(--text-mute); }
/* 「专辑 · 年份」：只有音频内嵌标签里读到才出现，读不到时整行 hidden 不占位。
   负的上边距是为了塞进歌手和进度条之间，不把原来的间距撑开。 */
.player-album {
  margin: -12px 0 18px;
  font-size: 12px;
  color: var(--text-mute);
  opacity: 0.78;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.player-album[hidden] { display: none; }

.progress {
  height: 6px;
  border-radius: 999px;
  background: rgba(120, 116, 130, 0.18);
  cursor: pointer;
  overflow: hidden;
}
.progress-bar {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--accent), #ffb08a);
  transition: width 0.2s linear;
}
.time-row {
  display: flex;
  justify-content: space-between;
  margin: 8px 2px 16px;
  font-size: 12px;
  color: var(--text-mute);
}
/* align-items:center -> 五键（含放大后的播放键）排在同一条水平线上；
   justify-content:center -> 五键作为一整组，在进度条下方居中（第四轮） */
.controls { display: flex; gap: 12px; align-items: center; justify-content: center; }
.ctrl {
  width: 46px; height: 46px;
  border-radius: 50%;
  font-size: 16px;
  color: #fff;
  display: inline-flex;          /* 图标靠 flex 居中，不然会偏到左上角 */
  align-items: center;
  justify-content: center;
  padding: 0;
  background: linear-gradient(135deg, var(--accent), #ff9a7a);
  box-shadow: 0 8px 22px rgba(224, 90, 82, 0.32);
  transition: transform 0.25s ease;
}
.ctrl:hover { transform: scale(1.06); }
/* 播放/暂停键：圆形底 ×2、图标 ×2（第四轮把 3 倍改回 2 倍）。其他键 46px / 图标约 21.6px。 */
.ctrl-play { width: 92px; height: 92px; }
.ctrl-play .ic { width: 43px; height: 43px; }
@media (max-width: 720px) {
  .ctrl-play { width: 72px; height: 72px; }
  .ctrl-play .ic { width: 33px; height: 33px; }
  .controls { gap: 8px; }
}

.track-list {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
}
.track-item {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 18px;
  margin-bottom: 10px;
  border-radius: var(--radius-sm);
  background: var(--glass);
  border: 1px solid var(--glass-border);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  cursor: pointer;
  /* 2026-10-09 第二轮：曲目行不再单独做悬停倾斜/推移（倾斜交给整块 .music-panel 卡），
     只保留背景变亮的悬停反馈；下面 transform 里的变量留给别处复用，默认都是 0。 */
  transform: perspective(900px)
             rotateX(var(--card-rx, 0deg))
             rotateY(var(--card-ry, 0deg))
             scale(var(--card-scale, 1))
             translateX(var(--track-nudge, 0px));
  transition: background 0.3s ease, transform 0.3s ease;
}
.track-item:hover { background: var(--glass-strong); }
/* 鼠标停在行内时换短过渡，跟手要快；移开摘掉这个类，回正走上面那 0.3s */
.track-item.is-tilting { transition: transform 0.12s ease-out, background 0.3s ease; will-change: transform; }
.track-item.is-active { border-color: rgba(224, 90, 82, 0.45); }
.track-idx { color: var(--accent); font-size: 15px; }
.track-meta { flex: 1; min-width: 0; }
.track-meta strong { display: block; font-size: 14px; }
/* 没歌手的那几条也得占住这一行：空 <small> 是 inline，不占位，
   于是带歌手的行会比别的高出一行，整个列表就参差不齐了 */
.track-meta small { display: block; min-height: 1.2em; color: var(--text-mute); font-size: 12px; }
.track-dur { font-size: 12px; color: var(--text-mute); }

/* 当前曲目那一项：自己那行收起来，改成内嵌的播放器卡（卡由 JS 搬进这个 li） */
.track-item.is-current {
  display: block;
  padding: 0;
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  cursor: default;
  margin-bottom: 16px;
}
.track-item.is-current:hover { background: transparent; transform: none; }
.track-item.is-current > .track-idx,
.track-item.is-current > .track-meta,
.track-item.is-current > .track-dur { display: none; }

/* ---------------- 音乐页 · 两栏版式（2026-10-09 第二轮） ----------------
   左：播放信息卡；右：歌词 / 歌单卡。两块卡互相独立，宽屏 5:7 并排，窄屏自动上下堆叠。 */
.music-layout {
  display: grid;
  grid-template-columns: 1fr 1fr;   /* 两卡等宽（第三轮 #4） */
  gap: 22px;
  align-items: start;
}
.music-layout > .music-panel { margin-top: 0; }   /* 间距交给上面的 gap */
@media (max-width: 1100px) {
  .music-layout { grid-template-columns: 1fr; gap: 18px; }
}

/* ---------------- 音乐页 · 歌词 / 歌单切换面板 ----------------
   半透明卡片，和全站材质一致。切换按钮在上，下面两个面板互斥显示（不打断播放）。 */
.music-panel {
  margin-top: 22px;
  padding: 22px 24px 24px;
  /* 卡内纵向布局：标题行固定，两个面板平分剩余高度、各自内部滚动（第三轮 #2/#4） */
  display: flex;
  flex-direction: column;
  overflow: hidden;            /* 高度由 JS 定死，超出部分裁掉、交给内部滚动 */
  height: auto;                /* 拿不到 JS 高度时退化成自然高度 */
}
.music-pane { flex: 1 1 auto; min-height: 0; }
#paneLyric:not([hidden]) { display: flex; flex-direction: column; }
#paneList { overflow: hidden; }
.music-tabs {
  display: flex;
  gap: 8px;
  margin-bottom: 18px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--glass-border);
}
.mtab {
  padding: 7px 20px;
  font-size: 14px;
  font-family: inherit;
  color: var(--text-soft);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  cursor: pointer;
  transition: color 0.25s ease, background 0.25s ease, box-shadow 0.25s ease;
}
.mtab:hover { color: var(--accent); }
.mtab.is-on {
  color: #fff;
  background: linear-gradient(135deg, var(--accent), #ff9a7a);
  box-shadow: 0 6px 16px rgba(224, 90, 82, 0.28);
}
.music-pane[hidden] { display: none; }

/* 歌词窗口：固定高度、内部滚动，当前句由 app.js 滚到中间。
   每一句的字号 / 颜色 / 描边等都取 site-config.js 的 --lyric-* 变量。 */
.lyric-box {
  flex: 1 1 auto;
  min-height: 200px;
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--lyric-pad, 28px 10px);
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}
.lyric-box::-webkit-scrollbar { width: 6px; }
.lyric-box::-webkit-scrollbar-thumb { background: rgba(120, 116, 130, 0.28); border-radius: 999px; }
.lyric-inner { display: flex; flex-direction: column; }
/* 上下各留半窗留白：第一句/最后一句也能滚到窗口正中（第三轮 #3）。
   --lyric-ctr 由 app.js 按歌词窗实际高度写入。 */
.lyric-inner::before,
.lyric-inner::after { content: ''; display: block; height: var(--lyric-ctr, 130px); flex: 0 0 auto; }
.lyric-line {
  margin: 0;
  padding: 2px 0;
  font-size: var(--lyric-size, 14px);
  font-weight: var(--lyric-weight, 400);
  color: var(--lyric-color, rgba(95, 92, 104, 0.7));
  opacity: var(--lyric-opacity, 0.85);
  line-height: 1.7;
  text-align: center;
  white-space: pre-wrap;
  word-break: break-word;
  -webkit-text-stroke: var(--lyric-stroke, 0px transparent);
  transition: color var(--lyric-dur, 0.28s) ease,
              font-size var(--lyric-dur, 0.28s) ease,
              opacity var(--lyric-dur, 0.28s) ease,
              text-shadow var(--lyric-dur, 0.28s) ease;
}
.lyric-line + .lyric-line { margin-top: var(--lyric-gap, 14px); }
.lyric-line.is-now {
  font-size: var(--lyric-now-size, 19px);
  font-weight: var(--lyric-now-weight, 700);
  color: var(--lyric-now-color, #e05a52);
  opacity: var(--lyric-now-opacity, 1);
  -webkit-text-stroke: var(--lyric-now-stroke, 0px transparent);
  /* 白字外发光：模糊半径取 site-config.js 的 --lyric-now-glow（默认 5px）；
     黑字叠白光形成描边式光晕，字号和颜色都不动。 */
  text-shadow: var(--lyric-now-glow, 0 0 5px rgba(255, 255, 255, 0.95));
}
.lyric-empty { margin: 0; padding: 40px 0; text-align: center; font-size: 13px; color: var(--text-mute); }

/* 歌单在面板里，去掉原先把列表贴在播放器卡下面的那段外边距 */
.track-list-music { margin: 0; height: 100%; overflow-y: auto; padding-right: 6px; scrollbar-width: thin; }
.track-list-music::-webkit-scrollbar { width: 6px; }
.track-list-music::-webkit-scrollbar-thumb { background: rgba(120, 116, 130, 0.28); border-radius: 999px; }

/* 音量：按钮 + 滑块。滑块默认不显示，点音量键后才在按钮右侧滑出（第三轮 #1）；
   停手 5 秒或点卡片外自动收起，由 app.js 控制。 */
.vol-wrap { display: flex; align-items: center; gap: 8px; }
.vol-range {
  display: none;
  width: var(--vol-w, 86px);
  height: 4px;
  margin: 0;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: rgba(120, 116, 130, 0.22);
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
}
.vol-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 13px; height: 13px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 2px 6px rgba(224, 90, 82, 0.4);
  cursor: pointer;
}
.vol-range::-moz-range-thumb {
  width: 13px; height: 13px; border: none; border-radius: 50%;
  background: var(--accent); cursor: pointer;
}
.vol-wrap.is-open .vol-range { display: block; }

/* 乐迷留言板：沿用文章留言区那套（.art-comment-*），只换个外壳和内边距 */
.music-comments { margin-top: 22px; padding: 26px 28px 28px; }
.music-comments-title { margin: 0 0 6px; font-size: 17px; color: var(--text); }
.music-comments-sub { margin: 0 0 20px; font-size: 13px; color: var(--text-mute); }

@media (max-width: 720px) {
  .music-panel { margin-top: 16px; padding: 18px 16px 20px; }
  .music-comments { margin-top: 16px; padding: 20px 16px 22px; }
  .controls { flex-wrap: wrap; }
}

/* ---------------- 灵感引用 ---------------- */
.credit-card { padding: 30px 32px; }
.credit-card h3 { margin: 0 0 14px; font-size: 19px; color: var(--accent); }
.credit-card p { margin: 0 0 18px; font-size: 14px; line-height: 1.9; color: var(--text-soft); }
.credit-list { list-style: none; margin: 0 0 18px; padding: 0; }
.credit-list li {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 11px 0;
  border-bottom: 1px dashed rgba(120, 116, 130, 0.22);
  font-size: 14px;
}
.credit-list li span { flex: 0 0 76px; color: var(--text-mute); font-size: 13px; }
.credit-list li a { color: var(--accent); word-break: break-all; }
.credit-list li a:hover { text-decoration: underline; }
.credit-note { margin: 0; font-size: 12.5px; color: var(--text-mute); line-height: 1.9; }
.credit-card + .credit-card { margin-top: 18px; }

/* ---------------- 迷你播放器 ---------------- */
.mini-player {
  position: fixed;
  right: 22px;
  bottom: 22px;
  z-index: 120;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px 10px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.62);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  backdrop-filter: blur(18px) saturate(1.3);
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-lg);
  animation: pageIn 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
.mini-toggle {
  width: 38px; height: 38px;
  border-radius: 50%;
  color: #fff;
  font-size: 13px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: linear-gradient(135deg, var(--accent), #ff9a7a);
}
.mini-info { display: flex; flex-direction: column; line-height: 1.35; }
.mini-info strong { font-size: 12.5px; }
.mini-info small { font-size: 11px; color: var(--text-mute); }
.mini-close {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 4px 6px; font-size: 15px; color: var(--text-mute);
}
.mini-close:hover { color: var(--accent); }
/* 迷你窗里的图标单给个尺寸：圆钮只有 38px，按 1.2em 算太小了 */
.mini-toggle .ic { width: 1.5em; height: 1.5em; }
.mini-close .ic { width: 1.1em; height: 1.1em; }

/* ---------------- 页脚 ---------------- */
.footer {
  position: relative;
  z-index: 10;
  padding: 0px 5% 0px;
  text-align: center;
  font-size: 12.5px;
  color: var(--text-mute);
}
.footer p { margin: 0 0 6px; }
.footer-sub a { color: var(--accent); }
.footer-sub a:hover { text-decoration: underline; }

/* ---------------- 响应式 ---------------- */
@media (max-width: 860px) {
  .nav-toggle { display: block; }
  .nav-links {
    position: fixed;
    top: var(--nav-h);
    left: 0; right: 0;
    flex-direction: column;
    gap: 0;
    padding: 8px 5% 18px;
    background: rgba(255, 255, 255, 0.72);
    -webkit-backdrop-filter: blur(22px) saturate(1.3);
    backdrop-filter: blur(22px) saturate(1.3);
    border-bottom: 1px solid rgba(255, 255, 255, 0.6);
    box-shadow: 0 14px 30px rgba(48, 42, 60, 0.1);
    transform: translateY(-12px);
    opacity: 0;
    visibility: hidden;
    transition: all 0.35s cubic-bezier(0.22, 1, 0.36, 1);
  }
  .nav-links.is-open { transform: translateY(0); opacity: 1; visibility: visible; }
  .nav-links a { padding: 13px 2px; border-bottom: 1px solid rgba(120, 116, 130, 0.1); }
  .nav-links a:last-child { border-bottom: none; }
  .nav-links a.is-active::after { left: auto; right: 2px; top: 50%; bottom: auto; margin: -2px 0 0; }

  .wrap { padding-top: calc(var(--nav-h) + 28px); }
  .hero-inner { padding-bottom: 4vh; }
  .hero-title { margin-bottom: 12px; letter-spacing: -0.6px; }
  /* 手机上波浪静止，省性能 */
  .frost-wave { animation: none; }
  .music-controls { margin-top: 16px; }
  .player-card { flex-direction: column; text-align: center; padding: 24px 20px; }
  .controls { justify-content: center; }
  .credit-card { padding: 24px 20px; }
  .credit-list li { flex-direction: column; gap: 4px; }
  .credit-list li span { flex: none; }
  .mini-player { right: 14px; bottom: 14px; }
  .lb-prev { left: 2vw; }
  .lb-next { right: 2vw; }
}

@media (max-width: 480px) {
  .page-title { font-size: 23px; }
  .photo-grid { grid-template-columns: repeat(2, 1fr); }
  .widget-card { padding: 18px 18px 20px; }
  .cal-day { min-height: 27px; font-size: 12px; }
}

/* ================= 文章页 ================= */
/* 排列切换按钮 */
.art-bar { display: flex; gap: 10px; margin: 0 0 22px; }
.art-view {
  padding: 7px 18px; font-size: 13px; border-radius: 999px;
  color: var(--text-soft); background: var(--glass);
  border: 1px solid var(--glass-border);
  transition: color .25s ease, background .25s ease, border-color .25s ease;
}
.art-view:hover { color: var(--text); }
.art-view.is-on { color: #fff; background: var(--accent); border-color: var(--accent); }
/* 分类筛选行：文章分类（tags）横向依次排开。
   点中一项 → 高亮 + 外发光，只看这一类；再点同一项 → 取消筛选、恢复全部。
   条目由 app.js 的 buildCatChips() 生成，切换矩阵 / 时间轴不影响选中状态。 */
.art-cats { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 22px; }
.art-cat {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 15px; font-size: 13px; border-radius: 999px;
  color: var(--text-soft); background: var(--glass);
  border: 1px solid var(--glass-border); cursor: pointer;
  transition: color .25s ease, background .25s ease, border-color .25s ease,
              box-shadow .3s ease, transform .25s ease;
}
.art-cat:hover { color: var(--text); transform: translateY(-1px); }
.art-cat em { font-style: normal; font-size: 12px; opacity: .7; }
.art-cat.is-on {
  color: #fff; background: var(--accent); border-color: var(--accent);
  box-shadow: 0 0 14px rgba(224, 90, 82, .5), 0 0 0 3px rgba(224, 90, 82, .16);
}
.art-cat.is-on em { opacity: .9; }
.art-empty { color: var(--text-mute); font-size: 14px; padding: 40px 0; text-align: center; }

/* 排列一：矩阵（一行 5 篇，文章之间间距 20px）
   2026-10-08 老大要求：由一行 3 篇改为一行 5 篇。卡片自带 aspect-ratio: 3/4，
   列一变窄就自动等比缩小，不需要另外写尺寸。 */
.art-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px; }
/* JS 用 hidden 切换两个容器；display:grid/flex 会盖掉 [hidden]，必须补这条 */
.art-grid[hidden], .art-timeline[hidden] { display: none; }

/* 文章卡：上 70% 截图 / 中 10% 间隙 / 下 20% 标题与时间 */
.art-card {
  display: flex; flex-direction: column;
  aspect-ratio: 3 / 4;
  padding: 0; overflow: hidden; cursor: pointer;
}
.art-shot { height: 80%; padding: 15px; }   /* 白底距卡片边框 15px */
/* 白底纸面：正文从左上角开始排，超出部分直接裁掉 */
.art-paper {
  height: 100%; padding: 10px;              /* 正文距白底边 10px */
  background: #fff; border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 1px 4px rgba(0,0,0,.07);
}
.art-shot-inner {
  color: var(--text-soft);
  font-size: 13px; line-height: 1.75;
  pointer-events: none;
}
.art-shot-inner > *:first-child { margin-top: 0; }
.art-shot-inner h1 { font-size: 17px; color: var(--text); margin: .1em 0 .4em; }
.art-shot-inner h2 { font-size: 15px; color: var(--text); margin: .5em 0 .3em; }
.art-shot-inner h3 { font-size: 14px; color: var(--text); margin: .5em 0 .3em; }
.art-shot-inner p, .art-shot-inner li { font-size: 13px; line-height: 1.75; margin: .5em 0; }
.art-shot-inner ul, .art-shot-inner ol { padding-left: 1.4em; }
.art-shot-inner img { max-width: 100%; border-radius: 6px; }
.art-shot-inner code, .art-shot-inner pre { font-family: Consolas, monospace; font-size: 11.5px; background: rgba(0,0,0,.05); border-radius: 6px; }
.art-shot-inner pre { padding: 8px; overflow: hidden; }
.art-shot-inner table { border-collapse: collapse; width: 100%; font-size: 11.5px; }
.art-shot-inner th, .art-shot-inner td { border: 1px solid rgba(0,0,0,.08); padding: 3px 6px; text-align: left; }
.art-shot-inner blockquote { margin: .5em 0; padding-left: 10px; border-left: 3px solid var(--accent-soft); color: var(--text-mute); }
.art-spacer { display: none; }   /* 白底直接顶到标题区，不再夹一层空白 */
.art-meta {
  height: 20%; padding: 15px;                /* 标题距边框 15px */
  display: flex; flex-direction: column; justify-content: center; gap: 6px;
  border-top: 1px solid var(--glass-border);
}
.art-title {
  font-size: 15px; font-weight: 700; color: var(--text); line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.art-date { font-size: 12px; color: var(--text-mute); }

/* 排列二：纵向时间轴（左右交替） */
.art-timeline { position: relative; }
.art-timeline::before {
  content: ''; position: absolute; top: 0; bottom: 0; left: 50%; width: 2px; margin-left: -1px;
  background: linear-gradient(to bottom, rgba(224,90,82,0), var(--accent-soft), rgba(224,90,82,0));
  border-radius: 2px;
}
.art-tl-item { position: relative; display: flex; align-items: center; width: 100%; margin-bottom: 26px; }
/* 2026-10-08 老大要求：时间轴卡片缩到原来的一半宽（50% - 46px → 25% - 23px），
   高度跟着 aspect-ratio 等比缩；卡片到中轴线的距离和日期侧栏一致，留 100px。
   定位改用「固定右外边距 + auto 左外边距」：卡片右边缘 = 100% - (50% + 100px)，
   正好落在中轴左侧 100px。这样宽度成了独立变量，窄屏放宽卡片时只改 width，
   不用跟着重算外边距（上一版写 margin-left: 25% - 77px 就得联动改）。
   右侧卡片 .is-right 走 row-reverse，镜像处理。
   中轴留白在窄屏会收到 80px / 60px，见文件末尾的媒体查询。 */
.art-tl-item .art-card {
  width: calc(25% - 23px);
  margin-left: auto;
  margin-right: calc(50% + 100px);
}
.art-tl-item.is-right .art-card {
  margin-left: calc(50% + 100px);
  margin-right: auto;
}
/* 日期靠时间轴这一侧对齐，距中线 100px */
.art-tl-side {
  position: absolute; top: 50%; transform: translateY(-50%);
  left: 50%; right: 0; padding-left: 100px;
  display: flex; flex-direction: column; justify-content: center;
  align-items: flex-start; text-align: left;
}
.art-tl-item.is-right { flex-direction: row-reverse; }
.art-tl-item.is-right .art-tl-side {
  left: 0; right: 50%; padding-left: 0; padding-right: 100px;
  align-items: flex-end; text-align: right;
}
/* 圆点落在卡片纵向中心：.art-tl-item 垂直居中，故 50% 即卡片中心 */
.art-tl-dot {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--accent); border: 2px solid #fff;
  box-shadow: 0 0 0 4px rgba(224,90,82,.18);
}
/* 2026-10-08 老大要求：日期和它下面的分类（.art-tl-sub，渲染的是文章 tags）
   共用一个半透明底，样式沿用文章预览卡那套毛玻璃。
   背景挂在 .art-tl-chip 这层，不挂在日期上，否则文字换行或加第二行时底色会断开。
   宽度 fit-content 跟着内容走，不撑满整条侧栏；靠左还是靠右仍由 .art-tl-side 的
   align-items 决定，左右交替时不用另写规则，文字对齐靠 text-align 继承下来。
   圆角从 999px 收到站点变量 --radius-sm，呈圆角矩形而不是胶囊。 */
.art-tl-chip {
  width: fit-content;
  padding: 8px 14px;
  border-radius: var(--radius-sm, 10px);
  background: var(--glass); border: 1px solid var(--glass-border);
}
.art-tl-time { font-size: 18px; color: var(--accent); }
.art-tl-sub { font-size: 12px; color: var(--text-mute); margin-top: 4px; }

/* 文章阅读组件（站内切换，不再全屏覆盖）
   纸面宽度 = min(--art-read-width, 屏宽 - --art-read-pad × 2)，两个值都在 site-config.js 里改；
   白底纸面 .art-reader-sheet 里装正文 + 留言区，纸面内边距 20px。
   返回栏 .art-reader-bar 在纸面外面，左缘与「矩阵」按钮对齐。 */
.art-reader {
  /* --art-read-width（site-config.js，默认 1230px）是纸面最大宽度；
     屏幕不够宽时先按 --art-read-pad 留出左右白边，纸面自动收窄。
     .wrap 本身只有 90% 宽，所以宽度与左右 margin 一起算，让纸面在屏幕上真正居中。 */
  --read-w: min(var(--art-read-width, 1230px), calc(100vw - var(--art-read-pad, 100px) * 2));
  width: var(--read-w);
  max-width: none;
  margin: 0 calc((100% - var(--read-w)) / 2) 70px;
}
.art-reader[hidden] { display: none; }
/* 返回栏从纸面里挪到 .wrap 直下（2026-11-02）：左缘和「矩阵」按钮那条对齐。
   display:flex 会盖掉 hidden 属性，所以要单独补一条 [hidden]。 */
.art-reader-bar {
  display: flex; align-items: center; gap: 14px; margin-bottom: 14px;
}
.art-reader-bar[hidden] { display: none; }
.art-reader-back {
  padding: 6px 16px; font-size: 13px; border-radius: 999px;
  color: var(--text-soft); background: var(--glass); border: 1px solid var(--glass-border);
  cursor: pointer;
}
.art-reader-back:hover { color: var(--accent); }
.art-reader-title { font-size: 14px; color: var(--text-mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 白底纸面：正文 + 留言区都在里面。文字离纸边多远由 --art-read-inset / --art-read-inset-y 控制（site-config.js）。 */
.art-reader-sheet {
  padding: var(--art-read-inset-y, 56px) var(--art-read-inset, 72px);
  background: #fff;
  border-radius: var(--radius, 24px);
  box-shadow: 0 10px 34px rgba(48, 42, 60, .08);
}
.art-reader-body { padding: 0; line-height: 1.85; color: var(--text-soft); }

/* 留言区：列表 + 留言窗口 */
.art-comments { margin-top: 34px; padding-top: 20px; border-top: 1px solid var(--glass-border); }
.art-comments-title { margin: 0 0 14px; font-size: 16px; color: var(--text); }
.art-comment-list { display: flex; flex-direction: column; gap: 12px; }
.art-comment { padding: 12px 14px; border-radius: var(--radius-sm, 16px); background: rgba(48, 42, 60, .045); }
.art-comment-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 6px; }
.art-comment-name { font-size: 13px; font-weight: 700; color: var(--text); }
.art-comment-time { font-size: 12px; color: var(--text-mute); }
.art-comment-text { font-size: 14px; line-height: 1.7; color: var(--text-soft); white-space: pre-wrap; word-break: break-word; }
.art-comment-empty { padding: 6px 0; font-size: 13px; color: var(--text-mute); }
.art-comment-form { display: flex; flex-direction: column; gap: 10px; margin-top: 16px; }
.art-input {
  width: 100%; padding: 10px 12px; font-size: 14px; font-family: inherit; color: var(--text);
  background: rgba(48, 42, 60, .04); border: 1px solid var(--glass-border);
  border-radius: var(--radius-sm, 16px); outline: none;
}
.art-input:focus { border-color: var(--accent-soft); }
.art-textarea { min-height: 84px; resize: vertical; }
.art-comment-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.art-comment-tip { font-size: 12px; color: var(--text-mute); }
.art-comment-tip.is-error { color: var(--accent); }
.art-comment-send {
  padding: 8px 22px; font-size: 14px; font-weight: 600; border: none; border-radius: 999px;
  color: #fff; background: var(--accent); cursor: pointer;
}
.art-comment-send:disabled { opacity: .55; cursor: default; }
.art-reader-date { margin: -.6em 0 1.4em; font-size: 13px; color: var(--text-mute); }
.art-reader-body h1 { font-size: 26px; color: var(--text); }
.art-reader-body h2 { font-size: 21px; color: var(--text); margin-top: 1.6em; }
.art-reader-body h3 { font-size: 17px; color: var(--text); margin-top: 1.3em; }
.art-reader-body code { font-family: Consolas, monospace; font-size: 14px; background: rgba(0,0,0,.06); padding: 1px 5px; border-radius: 4px; }
.art-reader-body pre { background: rgba(0,0,0,.05); padding: 14px; border-radius: 10px; overflow-x: auto; }
.art-reader-body pre code { background: none; padding: 0; }
.art-reader-body blockquote { margin: 1em 0; padding: .2em 1em; border-left: 3px solid var(--accent-soft); color: var(--text-mute); }
.art-reader-body table { border-collapse: collapse; width: 100%; margin: 1em 0; font-size: 14px; }
.art-reader-body th, .art-reader-body td { border: 1px solid var(--glass-border); padding: 7px 10px; text-align: left; }
.art-reader-body th { background: rgba(224,90,82,.08); }
.art-reader-body img { max-width: 100%; border-radius: 10px; }
.art-reader-body a { color: var(--accent); }

/* 2026-10-29：正文改由后台富文本编辑器产出 HTML，统一在 .art-prose 里收口排版。
   文章标题由阅读层自己渲染，正文里再出现 h1 就压小一号，别和标题抢；
   图片默认块级居中，编辑器的行内对齐由内联样式优先。 */
.art-prose > *:first-child { margin-top: 0; }
.art-prose p { margin: 1em 0; }
.art-prose h1 { font-size: 22px; }
.art-prose h4, .art-prose h5, .art-prose h6 { color: var(--text); margin: 1.2em 0 .6em; }
.art-prose img { display: block; margin: 1.2em auto; }
.art-prose ul, .art-prose ol { margin: 1em 0; padding-left: 1.4em; }
.art-prose li { margin: .35em 0; }
.art-prose hr { border: 0; border-top: 1px solid var(--glass-border); margin: 1.8em 0; }
/* 整页模式：文章本身是一个完整 HTML 页面，用 iframe 隔离，高度交给内容自己长 */
.art-iframe { display: block; width: 100%; height: 78vh; min-height: 520px; border: 0;
  border-radius: 12px; background: transparent; }
@media (max-width: 640px) {
  .art-iframe { height: 80vh; min-height: 420px; }
}

/* 窄屏自适应：按「每列不低于 200px 上下」的密度逐档降列，别让卡片被挤成一条。
   1650px 内容宽时 5 列每列约 314px，下面各档算下来都落在 230~270px。 */
@media (max-width: 1100px) {
  .art-grid { grid-template-columns: repeat(4, 1fr); }
  /* 时间轴同步放宽：卡片变宽后中轴留白从 100px 收到 80px，
     否则「卡片 + 留白」会超过半个容器。日期侧栏的 padding 要跟着收，不然会和卡片叠上。 */
  .art-tl-item .art-card { width: calc(33% - 20px); margin-right: calc(50% + 80px); }
  .art-tl-item.is-right .art-card { margin-left: calc(50% + 80px); }
  .art-tl-side { padding-left: 80px; }
  .art-tl-item.is-right .art-tl-side { padding-right: 80px; }
}
@media (max-width: 900px) {
  .art-grid { grid-template-columns: repeat(3, 1fr); }
  /* 再放宽一档。640px 以下整条时间轴会改成「线靠左、卡片全宽」，不在这两档里管。 */
  .art-tl-item .art-card { width: calc(40% - 30px); margin-right: calc(50% + 60px); }
  .art-tl-item.is-right .art-card { margin-left: calc(50% + 60px); }
  .art-tl-side { padding-left: 60px; }
  .art-tl-item.is-right .art-tl-side { padding-right: 60px; }
  /* 900 以下纸面交回 100%（.wrap 内），不再按 --art-read-pad 留 100px */
  .art-reader { width: 100%; max-width: 100%; margin-left: 0; margin-right: 0; }
}
@media (max-width: 640px) {
  .art-grid { grid-template-columns: repeat(2, 1fr); gap: 14px; }
  .art-bar { margin-bottom: 16px; }
  .art-cats { margin-bottom: 16px; }
  /* 窄屏时间轴：线靠左，卡片统一在右 */
  .art-timeline::before { left: 8px; }
  .art-tl-item, .art-tl-item.is-right { flex-direction: column; align-items: stretch; }
  .art-tl-item .art-card,
  .art-tl-item.is-right .art-card { width: calc(100% - 34px); margin-left: 34px; margin-right: 0; }
  .art-tl-side, .art-tl-item.is-right .art-tl-side {
    position: static; transform: none; left: auto; right: auto;
    width: calc(100% - 34px); margin-left: 34px; padding: 0;
    align-items: flex-start; text-align: left; margin-bottom: 6px; order: -1;
  }
  .art-tl-dot { left: 8px; top: 22px; transform: translate(-50%, 0); }
  /* 卡片在窄屏只有 150px 上下，15px 内边距会把内容挤没，收一收；标题日期字号同步降一档 */
  .art-shot { padding: 10px; }
  .art-meta { padding: 10px; }
  .art-title { font-size: 13px; }
  .art-date { font-size: 11px; }
  .art-tl-time { font-size: 16px; }
}
/* 再窄就回到单列，两列已经装不下标题了 */
@media (max-width: 400px) {
  .art-grid { grid-template-columns: 1fr; }
}

/* 音乐卡内部「等比还原」（2026-10-29）：
   音乐卡从 x 宽（300 上下）压到一份宽（约 247，内容宽 207）后，
   卡内元素不再用写死的缩小值，而是按卡片内容宽等比缩放——
   基准是换位置之前那张卡（内容宽 260）的比例：
   封面 90/260、行距 14/260、主键 34/260…… 换算成 cqw 就是下面这些数。
   宽卡时 min() 的上限生效，和原来一模一样；窄卡时整张卡内部等比小一号，
   元素之间的相对位置、大小关系与换位置之前一致。
   改比例只改这些 cqw 数字；min() 里后半段的 px 是宽卡上限，别动。 */
.card-music .music-row { gap: min(5.2cqw, 14px); }
.card-music .music-cover {
  width: min(33.5cqw, 90px);
  height: min(33.5cqw, 90px);
}
.card-music .music-side {
  padding-left: min(2.2cqw, 6px);
  gap: min(3.1cqw, 8px);
}
.card-music .music-controls {
  gap: min(1.1cqw, 3px);
  flex-wrap: nowrap;   /* 五个键始终一行，等比缩到放得下为止 */
}
.card-music .ctrl-main {
  width: min(12.8cqw, 34px);
  height: min(12.8cqw, 34px);
  font-size: clamp(10px, 5.6cqw, 15px);
}
.card-music .ctrl-sm {
  width: min(9.4cqw, 25px);
  height: min(9.4cqw, 25px);
  font-size: clamp(8.5px, 3.75cqw, 10px);
}
.card-music .music-meta strong { font-size: clamp(10.5px, 5.4cqw, 14px); }
.card-music .music-meta small  { font-size: clamp(9.5px, 4.4cqw, 11.5px); }

/* 2026-10-29：顶部四张改成 2:1:1:1 后，平板宽度下「一份」只剩 ~200px，
   音乐卡和今日一览会挤成一团，这一段退回两列：
   个人简介 / 今日一览各跨满一行，音乐与正在做并排。手机单列断点（720）不受影响 */
@media (min-width: 721px) and (max-width: 1100px) {
  .home-top { grid-template-columns: 1fr 1fr; }
  .home-top > .card-profile,
  .home-top > .card-today { grid-column: 1 / -1; }
}

/* 尊重「减少动态效果」偏好 */
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
  .bg-slide { transition: none; }
}
