:root{
  /* 海洋底色 */
  --abyss:#04111d;
  --deep:#07273d;
  --ocean:#0a3a52;
  --accent:#48cae4;
  --accent-2:#0096c7;
  --accent-deep:#023e8a;
  /* 液态玻璃 */
  --glass-bg:rgba(255,255,255,.07);
  --glass-bg-strong:rgba(255,255,255,.11);
  --glass-border:rgba(150,210,255,.22);
  --glass-shadow:0 8px 32px rgba(0,0,0,.45);
  /* 文字 */
  --text:#eaf6fb;
  --text-dim:#9fc0d0;
  --text-faint:#7ba6b8;
  --radius:20px;
  --maxw:1080px;
  /* 间距系统(8pt 基准) */
  --space-1:8px; --space-2:16px; --space-3:24px; --space-4:32px;
  --space-5:48px; --space-6:64px; --space-7:80px; --space-8:96px;
  /* 模块化字阶 */
  --fs-xs:12.5px; --fs-sm:14px; --fs-base:16px; --fs-md:18px; --fs-lg:22px;
  --fs-xl:clamp(22px,3.5vw,30px); --fs-2xl:clamp(26px,5vw,40px);
  /* 缓动曲线 */
  --ease-out:cubic-bezier(.22,1,.36,1);
  --ease-in-out:cubic-bezier(.65,0,.35,1);
  /* 正文色（浅色模式下会重新取值） */
  --body-text:#cfe2ec;
  color-scheme:dark;
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{
  font-family:system-ui,-apple-system,"PingFang SC","Microsoft YaHei","Noto Sans CJK SC","Noto Sans SC","Hiragino Sans GB",sans-serif;
  color:var(--text);
  background:var(--abyss);
  line-height:1.7;
  overflow-x:hidden;
}
/* 海洋氛围背景(创新层) */
.ocean-bg{
  position:fixed;inset:0;z-index:-2;
  background:
    radial-gradient(circle at 20% 15%, rgba(14,74,110,.55), transparent 55%),
    radial-gradient(circle at 80% 85%, rgba(10,110,122,.45), transparent 50%),
    linear-gradient(160deg, #04111d 0%, #07273d 45%, #0a3a52 100%);
  background-size:200% 200%;
  animation:drift 22s ease-in-out infinite;
}
@keyframes drift{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
.orb{position:fixed;border-radius:50%;filter:blur(60px);z-index:-1;opacity:.5;pointer-events:none}
.orb.a{width:420px;height:420px;background:radial-gradient(circle,rgba(72,202,228,.22),transparent 70%);top:-120px;right:-80px}
.orb.b{width:360px;height:360px;background:radial-gradient(circle,rgba(2,62,138,.30),transparent 70%);bottom:-100px;left:-60px}

/* 液态玻璃(主力语言) */
.glass{
  background:var(--glass-bg);
  backdrop-filter:blur(16px) saturate(140%);
  -webkit-backdrop-filter:blur(16px) saturate(140%);
  border:1px solid var(--glass-border);
  border-radius:var(--radius);
  box-shadow:var(--glass-shadow), inset 0 1px 0 rgba(255,255,255,.12);
}

/* 跳转到主要内容（仅键盘 Tab 聚焦时出现）
   用 clip-path + 1px 把它从布局流里摘掉，而不是靠 left:-9999px 推出屏幕 ——
   负定位会被溢出检测认定为元素出血。同理不能用 display:none，那会让它无法被 Tab 聚焦。 */
.skip-link{
  position:absolute;top:0;left:0;z-index:100;
  width:1px;height:1px;overflow:hidden;
  clip-path:inset(50%);
  white-space:nowrap;
  padding:12px 20px;border-radius:0 0 12px 0;
  background:var(--accent-2);color:#04161f;
  font-size:14px;font-weight:600;text-decoration:none;
}
.skip-link:focus{
  width:auto;height:auto;overflow:visible;
  clip-path:none;
  outline:2px solid var(--accent);
  outline-offset:2px;
}
/* main 接收跳转焦点时不显示整块描边 */
main:focus{outline:none}
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 24px}
.nav{position:sticky;top:0;z-index:50;backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);background:rgba(4,17,29,.55);border-bottom:1px solid rgba(150,210,255,.12);transition:background .35s var(--ease-out),box-shadow .35s var(--ease-out)}
.nav.scrolled{background:rgba(4,17,29,.82);box-shadow:0 6px 28px rgba(0,0,0,.35)}
.nav .wrap{display:flex;align-items:center;justify-content:space-between;height:64px}
.brand{font-weight:700;letter-spacing:.04em;font-size:18px;color:var(--text);text-decoration:none}
.brand span{color:var(--accent)}
.nav-links{display:flex;gap:6px}
.nav-links a{color:var(--text-dim);text-decoration:none;padding:8px 14px;border-radius:999px;font-size:14px;transition:.2s}
.nav-links a:hover{color:var(--text);background:var(--glass-bg)}

.hero{min-height:88vh;min-height:88dvh;display:flex;align-items:center;justify-content:center;padding:var(--space-7) 24px}
.hero-card{max-width:680px;text-align:center;padding:48px 40px;animation:float 7s ease-in-out infinite}
@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}
.eyebrow{font-size:13px;letter-spacing:.2em;text-transform:uppercase;color:var(--accent);margin-bottom:18px;font-weight:600}
.hero h1{font-size:clamp(26px,5vw,40px);line-height:1.35;font-weight:700;margin-bottom:18px;text-wrap:balance}
.hero p.sub{color:var(--text-dim);font-size:16px;margin-bottom:30px}
.btns{display:flex;gap:14px;justify-content:center;flex-wrap:wrap}
.btn{padding:12px 26px;border-radius:999px;font-weight:600;font-size:15px;text-decoration:none;transition:.2s;border:1px solid transparent}
.btn-primary{background:linear-gradient(135deg,var(--accent),var(--accent-2));color:#04161f;box-shadow:0 6px 22px rgba(72,202,228,.35)}
.btn-primary:hover{transform:translateY(-2px)}
.btn:active{transform:translateY(0) scale(.97);transition-duration:.08s}
.btn-ghost{background:var(--glass-bg);border-color:var(--glass-border);color:var(--text)}
.btn-ghost:hover{background:var(--glass-bg-strong)}

section{padding:80px 0}
.section-title{font-size:clamp(22px,3.5vw,30px);text-align:center;margin-bottom:40px;font-weight:700}
.section-title small{display:block;color:var(--text-faint);font-size:14px;font-weight:400;margin-top:8px;letter-spacing:.1em}

.about{max-width:760px;margin:0 auto;text-align:center}
.about p{color:var(--text-dim);font-size:var(--fs-base);margin-bottom:18px;text-wrap:pretty}
.quote{
  font-size:clamp(20px,3vw,26px);font-weight:700;color:var(--text);
  margin:var(--space-4) auto;line-height:1.5;max-width:640px;
  padding-left:22px;border-left:3px solid var(--accent);text-align:left;
}
.quote::before{content:"“";color:var(--accent);margin-right:4px}
.quote::after{content:"”";color:var(--accent);margin-left:4px}

.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:22px}
.card{padding:28px;transition:.25s}
.card:hover{transform:translateY(-4px);border-color:rgba(72,202,228,.4)}
.card .ico{width:52px;height:52px;margin-bottom:14px;display:flex;align-items:center;justify-content:center;border-radius:14px;background:var(--glass-bg-strong);border:1px solid var(--glass-border)}
.card .ico svg{width:26px;height:26px;stroke:var(--accent);stroke-width:1.6;fill:none;stroke-linecap:round;stroke-linejoin:round}
.card h3{font-size:19px;margin-bottom:8px}
.card h3 a{color:var(--text);text-decoration:none}
.card .desc{color:var(--text-dim);font-size:14.5px;margin-bottom:14px}
.card .meta{font-size:12.5px;color:var(--text-faint);margin-bottom:16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.card .links{display:flex;gap:10px}
.card .links a{font-size:13px;color:var(--accent);text-decoration:none;padding:6px 12px;border:1px solid var(--glass-border);border-radius:999px}
.card .links a:hover{background:var(--glass-bg)}

.contact-grid{display:grid;grid-template-columns:1fr 1fr;gap:22px;max-width:880px;margin:0 auto}
.contact-grid .card h3{color:var(--accent);margin-bottom:12px}
.contact-grid ul{list-style:none}
.contact-grid li{padding:8px 0;color:var(--text-dim);font-size:14.5px;border-bottom:1px dashed rgba(150,210,255,.12)}
.contact-grid li:last-child{border-bottom:none}
.card-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:18px}
.card-actions .btn{padding:10px 20px;font-size:14px}
.card-note{margin-top:16px;padding-top:14px;border-top:1px dashed var(--glass-border);color:var(--text-faint);font-size:13.5px;line-height:1.65}

footer{padding:40px 0;text-align:center;color:var(--text-faint);font-size:13.5px;border-top:1px solid rgba(150,210,255,.1)}
footer a{color:var(--accent);text-decoration:none}
.lang{margin-bottom:12px}
.lang a{margin:0 6px}

.post-card{padding:28px;margin-bottom:22px;display:block;text-decoration:none;color:inherit;transition:.25s}
.post-card:hover{transform:translateY(-3px);border-color:rgba(72,202,228,.4)}
.post-card h3{font-size:20px;margin-bottom:8px;color:var(--text)}
.post-card .date{color:var(--text-faint);font-size:13px;margin-bottom:10px}
.post-card p{color:var(--text-dim);font-size:15px}

.article{max-width:760px;margin:0 auto;padding:40px 24px}
.article.glass{padding:44px}
.article h1{font-size:clamp(24px,4vw,34px);margin-bottom:10px;line-height:1.35}
.article .date{color:var(--text-faint);margin-bottom:30px;font-size:14px}
.article p{margin-bottom:18px;color:var(--body-text);font-size:16.5px}
.article h2{margin:34px 0 14px;font-size:22px;color:var(--text)}
.article code{background:var(--glass-bg);padding:2px 7px;border-radius:6px;font-family:ui-monospace,monospace;font-size:14px;color:var(--accent)}

/* 页脚签名 */
.footer-sign{margin-top:8px}
.sign{
  display:inline-block;color:var(--accent);letter-spacing:.08em;font-size:13.5px;
  padding:2px 2px 4px;
  background:linear-gradient(90deg,var(--accent),var(--accent-2)) bottom left/100% 1px no-repeat;
  background-size:100% 1px;
}
/* 文章内元素 */
.article ul,.article ol{margin:0 0 18px 22px;color:var(--body-text);font-size:16.5px}
.article li{margin-bottom:9px}
.article h3{margin:28px 0 12px;font-size:18px;color:var(--accent)}
.article table{width:100%;border-collapse:collapse;margin:0 0 22px;font-size:14.5px}
.article th,.article td{border:1px solid rgba(150,210,255,.18);padding:10px 12px;text-align:left;vertical-align:top}
.article th{background:rgba(255,255,255,.05);color:var(--text);font-weight:600}
.article td{color:var(--body-text)}
.article blockquote{margin:0 0 20px;padding:14px 18px;border-left:3px solid var(--accent);background:var(--glass-bg);border-radius:0 12px 12px 0;color:var(--text-dim)}
.article blockquote strong{color:var(--text)}
.article .tag{display:inline-block;font-size:12.5px;color:var(--accent);border:1px solid var(--glass-border);border-radius:999px;padding:4px 12px;margin:0 8px 14px 0}
.article .back{display:inline-block;margin-top:8px;font-size:14px;color:var(--accent);text-decoration:none}
.article .back:hover{text-decoration:underline}
.article .links{margin-top:28px;padding-top:20px;border-top:1px solid rgba(150,210,255,.14)}
.article .links a{font-size:14px;color:var(--accent);text-decoration:none;padding:6px 14px;border:1px solid var(--glass-border);border-radius:999px;margin-right:10px;display:inline-block;margin-bottom:10px}
.article .links a:hover{background:var(--glass-bg)}

/* .reveal 的初始态、过渡与错落延迟统一由文件末尾的「微动效系统」定义，
   那里是全站时长与位移的唯一来源。此处不留副本，避免两套规则打架。 */

@media(max-width:980px){
  section{padding:68px 0}
  .hero{padding:var(--space-6) 24px}
}
@media(max-width:720px){
  .grid,.contact-grid{grid-template-columns:1fr}
  .hero-card{padding:36px 22px}
  section{padding:60px 0}

  /* 导航栏：加了设置按钮后，窄屏必须防止换行挤压 */
  .nav .wrap{gap:8px}
  .brand{white-space:nowrap;flex-shrink:0}
  .nav-links{
    flex-wrap:nowrap;
    overflow-x:auto;               /* 极窄屏可横向滑动，而不是竖排换行 */
    scrollbar-width:none;          /* Firefox */
    -ms-overflow-style:none;
  }
  .nav-links::-webkit-scrollbar{display:none}
  .nav-links a{
    padding:6px 8px;font-size:13px;white-space:nowrap;flex-shrink:0;
  }
  .prefs{flex-shrink:0}
}

/* 极窄屏（<420px）：四个导航项确实放不下，隐藏纯文字链接，
   设置按钮里已包含「外观」等入口，站点主要入口仍在首屏按钮上 */
@media(max-width:430px){
  .nav-links a{padding:6px 6px;font-size:12.5px}
}

@media(prefers-reduced-motion:reduce){
  .ocean-bg,.orb,.hero-card{animation:none!important}
  .reveal{opacity:1;transform:none;transition:none}
  html{scroll-behavior:auto}
}
/* 脚本不可用时兜底：
   .reveal 初始 opacity:0 靠 main.js 加 .in 才显现，没有脚本就永远是白板。
   @media(scripting:none) 在脚本被禁用/被剥离时生效，不依赖任何 JS。
   注意它与 prefers-reduced-motion 是两类不同用户，不能互相覆盖。 */
@media(scripting:none){
  .reveal{opacity:1;transform:none}
  .nav{box-shadow:none}
}

/* 键盘可达性:仅键盘 Tab 聚焦时显示清晰焦点环,鼠标点击不显示 */
a:focus-visible,button:focus-visible,input:focus-visible,textarea:focus-visible,
.btn:focus-visible,.brand:focus-visible,.nav-links a:focus-visible,
.card .links a:focus-visible,.article .links a:focus-visible,
.article .back:focus-visible,.post-card:focus-visible{
  outline:2px solid var(--accent);
  outline-offset:3px;
  border-radius:6px;
}

/* ============================================================
   主题系统
   ------------------------------------------------------------
   三档选择：跟随系统 / 强制深色 / 强制浅色。
   JS 把生效结果写到 <html data-theme="dark|light">，
   把用户的原始选择写到 <html data-theme-pref="system|dark|light">。

   为什么用属性而不是 @media 单独判断：
   用户显式选了「浅色」时，即使系统是深色也必须浅色 —— 单靠
   prefers-color-scheme 做不到这件事，必须有一个可覆盖的钩子。

   本块必须在所有基础规则之后（同权重靠源顺序取胜）。
   ============================================================ */

/* 显式选浅色 */
html[data-theme="light"]{
  --abyss:#eaf4fa;
  --deep:#d3e8f4;
  --ocean:#b9dcee;
  --accent:#036e91;
  --accent-2:#0b7fa8;
  --accent-deep:#023e8a;
  --glass-bg:rgba(255,255,255,.62);
  --glass-bg-strong:rgba(255,255,255,.82);
  --glass-border:rgba(3,110,145,.20);
  --glass-shadow:0 8px 28px rgba(7,39,61,.13);
  --text:#062a3d;
  --text-dim:#3d6076;
  --text-faint:#547689;
  --body-text:#173d52;
  color-scheme:light;
}

/* 跟随系统 + 系统是浅色：与上面同一份取值 */
@media(prefers-color-scheme:light){
  html[data-theme-pref="system"]{
    --abyss:#eaf4fa;
    --deep:#d3e8f4;
    --ocean:#b9dcee;
    --accent:#036e91;
    --accent-2:#0b7fa8;
    --accent-deep:#023e8a;
    --glass-bg:rgba(255,255,255,.62);
    --glass-bg-strong:rgba(255,255,255,.82);
    --glass-border:rgba(3,110,145,.20);
    --glass-shadow:0 8px 28px rgba(7,39,61,.13);
    --text:#062a3d;
    --text-dim:#3d6076;
    --text-faint:#547689;
    --body-text:#173d52;
    color-scheme:light;
  }
}

/* --- 浅色下的非变量类样式 --- */
html[data-theme="light"] .ocean-bg{
  background:
    radial-gradient(circle at 20% 15%, rgba(72,202,228,.30), transparent 55%),
    radial-gradient(circle at 80% 85%, rgba(2,62,138,.16), transparent 50%),
    linear-gradient(160deg, #eaf4fa 0%, #d3e8f4 45%, #b9dcee 100%);
}
html[data-theme="light"] .orb.a{background:radial-gradient(circle,rgba(72,202,228,.30),transparent 70%)}
html[data-theme="light"] .orb.b{background:radial-gradient(circle,rgba(2,62,138,.10),transparent 70%)}
html[data-theme="light"] .nav{
  background:rgba(234,244,250,.78);
  border-bottom-color:rgba(3,110,145,.14);
}
html[data-theme="light"] .nav.scrolled{
  background:rgba(234,244,250,.94);
  box-shadow:0 6px 24px rgba(7,39,61,.10);
}
html[data-theme="light"] .glass{
  box-shadow:var(--glass-shadow), inset 0 1px 0 rgba(255,255,255,.75);
}
html[data-theme="light"] .card:hover,
html[data-theme="light"] .post-card:hover{border-color:rgba(3,110,145,.42)}
html[data-theme="light"] .btn-primary{
  background:linear-gradient(135deg,#0b7fa8,#036e91);
  color:#fff;
  box-shadow:0 6px 18px rgba(3,110,145,.28);
}
html[data-theme="light"] .card .ico svg{stroke:#036e91}
html[data-theme="light"] footer{border-top-color:rgba(3,110,145,.14)}
html[data-theme="light"] .contact-grid li{border-bottom-color:rgba(3,110,145,.14)}
html[data-theme="light"] .article th,
html[data-theme="light"] .article td{border-color:rgba(3,110,145,.18)}
html[data-theme="light"] .article th{background:rgba(3,110,145,.06)}
html[data-theme="light"] .article code{background:rgba(3,110,145,.09)}
html[data-theme="light"] .skip-link{color:#fff}
html[data-theme="light"] .prefs-panel{
  background:rgba(255,255,255,.96);
  box-shadow:0 18px 48px rgba(7,39,61,.18);
}
html[data-theme="light"] a:focus-visible,
html[data-theme="light"] button:focus-visible,
html[data-theme="light"] .btn:focus-visible,
html[data-theme="light"] .brand:focus-visible,
html[data-theme="light"] .nav-links a:focus-visible,
html[data-theme="light"] .card .links a:focus-visible,
html[data-theme="light"] .article .links a:focus-visible,
html[data-theme="light"] .article .back:focus-visible,
html[data-theme="light"] .post-card:focus-visible{outline-color:#0b7fa8}

@media(prefers-color-scheme:light){
  html[data-theme-pref="system"] .ocean-bg{
    background:
      radial-gradient(circle at 20% 15%, rgba(72,202,228,.30), transparent 55%),
      radial-gradient(circle at 80% 85%, rgba(2,62,138,.16), transparent 50%),
      linear-gradient(160deg, #eaf4fa 0%, #d3e8f4 45%, #b9dcee 100%);
  }
  html[data-theme-pref="system"] .orb.a{background:radial-gradient(circle,rgba(72,202,228,.30),transparent 70%)}
  html[data-theme-pref="system"] .orb.b{background:radial-gradient(circle,rgba(2,62,138,.10),transparent 70%)}
  html[data-theme-pref="system"] .nav{
    background:rgba(234,244,250,.78);border-bottom-color:rgba(3,110,145,.14);
  }
  html[data-theme-pref="system"] .nav.scrolled{
    background:rgba(234,244,250,.94);box-shadow:0 6px 24px rgba(7,39,61,.10);
  }
  html[data-theme-pref="system"] .glass{
    box-shadow:var(--glass-shadow), inset 0 1px 0 rgba(255,255,255,.75);
  }
  html[data-theme-pref="system"] .card:hover,
  html[data-theme-pref="system"] .post-card:hover{border-color:rgba(3,110,145,.42)}
  html[data-theme-pref="system"] .btn-primary{
    background:linear-gradient(135deg,#0b7fa8,#036e91);color:#fff;
    box-shadow:0 6px 18px rgba(3,110,145,.28);
  }
  html[data-theme-pref="system"] .card .ico svg{stroke:#036e91}
  html[data-theme-pref="system"] footer{border-top-color:rgba(3,110,145,.14)}
  html[data-theme-pref="system"] .contact-grid li{border-bottom-color:rgba(3,110,145,.14)}
  html[data-theme-pref="system"] .article th,
  html[data-theme-pref="system"] .article td{border-color:rgba(3,110,145,.18)}
  html[data-theme-pref="system"] .article th{background:rgba(3,110,145,.06)}
  html[data-theme-pref="system"] .article code{background:rgba(3,110,145,.09)}
  html[data-theme-pref="system"] .skip-link{color:#fff}
  html[data-theme-pref="system"] .prefs-panel{
    background:rgba(255,255,255,.96);
    box-shadow:0 18px 48px rgba(7,39,61,.18);
  }
  html[data-theme-pref="system"] .prefs-seg button.is-active{color:#fff}
}

/* ============================================================
   设置面板
   ============================================================ */
.prefs{position:relative}
.prefs-toggle{
  display:flex;align-items:center;justify-content:center;
  width:36px;height:36px;margin-left:8px;
  border:1px solid var(--glass-border);border-radius:999px;
  background:var(--glass-bg);color:var(--text-dim);
  cursor:pointer;transition:.2s;
}
.prefs-toggle:hover{color:var(--text);background:var(--glass-bg-strong)}
.prefs-toggle svg{width:18px;height:18px;stroke:currentColor;stroke-width:1.7;fill:none;stroke-linecap:round;stroke-linejoin:round}
.prefs-panel{
  position:absolute;top:calc(100% + 10px);right:0;z-index:60;
  width:min(320px,calc(100vw - 32px));
  padding:20px;
  /* 面板浮在正文之上，必须比普通玻璃卡更实，否则背后文字会透上来干扰阅读 */
  background:rgba(6,26,42,.94);
  backdrop-filter:blur(20px) saturate(140%);
  -webkit-backdrop-filter:blur(20px) saturate(140%);
  box-shadow:0 18px 48px rgba(0,0,0,.5);
}
/* 开关态的显示/隐藏与展开动画统一交给文件末尾的「微动效系统」，
   那里用 visibility + opacity + transform 实现可过渡的进出场。 */
.prefs-panel h3{font-size:15px;margin-bottom:4px;color:var(--text)}
.prefs-panel h4{font-size:13px;color:var(--text-faint);font-weight:600;letter-spacing:.06em;margin:18px 0 8px;text-transform:uppercase}
.prefs-panel p{font-size:13.5px;color:var(--text-dim);line-height:1.6}
/* 选项下方的解释文案。比正文再小半档、去掉行高余量，
   目的是让它读起来像"注解"而不是"又一个选项"。 */
.prefs-hint{
  margin-top:8px;font-size:12.5px;line-height:1.55;
  color:var(--text-faint);
}

/* 三档主题按钮 */
.prefs-seg{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-top:10px}
/* 二档（动效开关）。为什么单独一个类而不是复用三列：
   两列用三列网格会留出一个空位，视觉上像是"少了一个选项"。
   这里显式声明列数，避免依赖 grid 的自动填充行为。 */
.prefs-seg-2{grid-template-columns:repeat(2,1fr)}
.prefs-seg button{
  display:flex;align-items:center;justify-content:center;
  box-sizing:border-box;min-height:38px;
  padding:8px 6px;font-size:13px;line-height:1.2;font-family:inherit;
  white-space:nowrap;
  border:1px solid var(--glass-border);border-radius:12px;
  background:var(--glass-bg);color:var(--text-dim);
  cursor:pointer;transition:.18s;
}
.prefs-seg button:hover{color:var(--text);background:var(--glass-bg-strong)}
.prefs-seg button.is-active{
  background:linear-gradient(135deg,var(--accent),var(--accent-2));
  color:#04161f;border-color:transparent;font-weight:600;
  box-shadow:0 4px 14px rgba(72,202,228,.30);
}
/* 浅色模式下 accent 变深，激活按钮改用白字 */
html[data-theme="light"] .prefs-seg button.is-active{color:#fff}
@media(prefers-color-scheme:light){
  html[data-theme-pref="system"] .prefs-seg button.is-active{color:#fff}
}

/* 阅读记录列表 */
.prefs-reading{margin-top:6px}
.prefs-list{list-style:none;margin:0}
.prefs-list li{
  display:flex;justify-content:space-between;align-items:center;gap:10px;
  padding:7px 0;font-size:13px;
  border-bottom:1px dashed var(--glass-border);
}
.prefs-list li:last-child{border-bottom:none}
.prefs-list a{color:var(--accent);text-decoration:none}
.prefs-list a:hover{text-decoration:underline}
.prefs-list time{color:var(--text-faint);font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.prefs-empty{font-size:13px;color:var(--text-faint);line-height:1.6}

/* 操作按钮 */
.prefs-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
.prefs-actions button{
  flex:1 1 auto;padding:9px 14px;font-size:13px;font-family:inherit;
  border:1px solid var(--glass-border);border-radius:12px;
  background:var(--glass-bg);color:var(--text);cursor:pointer;transition:.18s;
}
.prefs-actions button:hover{background:var(--glass-bg-strong)}
.prefs-actions .prefs-reset{color:#e07a7a;border-color:rgba(224,122,122,.35)}
.prefs-actions .prefs-reset.is-armed{
  background:#e07a7a;color:#fff;border-color:transparent;font-weight:600;
}
/* 屏幕阅读器专用（隐藏 file input） */
.visually-hidden{
  position:absolute;width:1px;height:1px;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;
}
.prefs-msg{
  margin-top:12px;font-size:12.5px;line-height:1.55;color:var(--text-faint);
  display:none;
}
.prefs-msg.is-visible{display:block}
.prefs-msg.is-warn{color:#e07a7a}
.prefs-storage{
  margin-top:14px;padding-top:12px;font-size:12.5px;color:var(--text-faint);
  border-top:1px solid var(--glass-border);line-height:1.6;
}
.prefs-storage.is-warn{color:#e07a7a}

@media(max-width:720px){
  .prefs-toggle{width:32px;height:32px;margin-left:4px}
  .prefs-panel{width:min(300px,calc(100vw - 24px));padding:16px}
}

/* ---------- 离线页(offline.html) ---------- */
/* 只用已有的类组合,这里只补离线页特有的部分 */
.offline-card{max-width:620px;margin:0 auto}
.offline-list{
  list-style:none;padding:0;margin:18px 0 0;
}
.offline-list li{
  padding:10px 0;border-bottom:1px solid var(--glass-border);
}
.offline-list li:last-child{border-bottom:0}
.offline-list a{color:var(--accent);text-decoration:none}
.offline-list a:hover{text-decoration:underline}
.offline-note{text-align:center;max-width:620px;margin:20px auto 0}
/* <button> 默认不继承 .btn 的字体与指针样式,补上 */
button.btn{font-family:inherit;cursor:pointer}
button.btn:disabled{opacity:.6;cursor:default;transform:none}

/* ---------- PWA 提示条(更新 / iOS 安装引导) ---------- */
/* 默认隐藏,由 pwa.js 按条件加 .is-visible。用 fixed 悬浮在底部,
   不占据文档流,避免插入后把页面高度顶掉导致布局跳动。 */
.pwa-update,.pwa-ios-hint{
  position:fixed;left:50%;bottom:20px;transform:translateX(-50%);
  z-index:60;
  display:none;align-items:center;gap:12px;
  padding:12px 16px;border-radius:14px;
  max-width:calc(100vw - 32px);
  font-size:14px;line-height:1.5;
}
.pwa-update.is-visible,.pwa-ios-hint.is-visible{display:flex}
.pwa-update{bottom:20px}
.pwa-ios-hint{bottom:74px}

.pwa-update button,.pwa-ios-close{
  font-family:inherit;font-size:13.5px;cursor:pointer;
  border-radius:999px;border:1px solid var(--glass-border);
  padding:7px 14px;background:var(--glass-bg);color:var(--text);
  transition:.2s;white-space:nowrap;
}
.pwa-update-go{
  background:linear-gradient(135deg,var(--accent),var(--accent-2));
  color:#04161f;border-color:transparent;font-weight:600;
}
.pwa-ios-close{
  padding:4px 10px;font-size:16px;line-height:1;
}
.pwa-update button:hover,.pwa-ios-close:hover{transform:translateY(-1px)}

@media(max-width:520px){
  .pwa-update{flex-wrap:wrap;justify-content:center;bottom:14px}
  .pwa-ios-hint{flex-wrap:wrap;justify-content:center;bottom:auto;top:14px}
}

/* ============================================================
   微动效系统（本块必须在文件最末尾，理由见下方「源顺序」说明）
   ------------------------------------------------------------
   设计目标只有一句：动效是「状态变化的说明书」，不是装饰。
   每一次位移、渐显、延迟，都要能让用户看清"什么东西变了、
   从哪里来、到哪里去"。任何只为好看的动效都应当被删掉。

   四层结构，自上而下：

     第 0 层 令牌层 —— 所有时长/缓动/位移的唯一取值来源
     第 1 层 编排层 —— 谁先动、谁后动、最多等多久
     第 2 层 降级层 —— 七条兜底路径，任何一条命中都不能白屏
     第 3 层 空间层 —— 按"有没有指针悬停能力"分流，不按屏幕宽度
     第 4 层 总开关 —— html[data-motion="off"] 一把归零

   为什么必须放在文件最末尾：
   本块用 html[data-motion=...] 和 .reveal 的选择器去覆盖前面
   的同权重规则。CSS 同权重时靠源顺序取胜，一旦上移，
   前面的 .reveal{transition:...} 会反过来盖住这里，动效开关直接失效。
   （浅色主题块当初踩过同一个坑，见上方主题系统的注释。）
   ============================================================ */

/* ---------- 第 0 层：令牌层 ---------- */
  /* 三档时长，覆盖"极小反馈 / 常规过渡 / 氛围运动"三类节奏。
     为什么是三档而不是到处写具体毫秒：
     散落的 0.2s / 0.25s / .35s 无法统一调优，改一处忘一处；
     全部收敛到变量后，调慢或调快整个站点只改这里。 */
:root{
  --dur-fast:140ms;   /* 按下、悬停这类瞬时反馈 */
  --dur-base:240ms;   /* 进场、位移等可感知的过渡 */
  --dur-slow:1600ms;  /* 背景呼吸这类氛围运动 */

  /* 缓动：出场用 --ease-out（先快后慢，收得干脆）
     入场用 --ease-in（先慢后快）
     --ease-both 用于需要来回往复的循环运动 */
  --ease-out:cubic-bezier(.22,1,.36,1);
  --ease-in:cubic-bezier(.4,0,.9,.35);
  --ease-both:cubic-bezier(.65,0,.35,1);

  /* 三档位移距离：小=反馈，中=进场，大=强调 */
  --shift-sm:2px;
  --shift-md:6px;
  --shift-lg:14px;

  /* 编排参数：同组元素依次启动的间隔，以及整组等待的上限。
     上限存在的意义见第 1 层说明。 */
  --stagger-step:40ms;
}

/* ---------- 第 1 层：编排层 ---------- */
/*
   分组错落（stagger）只靠 CSS 变量传递：
   动态列表靠 JS 写 --i（见 assets/main.js），静态列表靠 nth-child。
   为什么不用 transition-delay 写死 nth-child 到第 8 个：
   博客列表的文章数会变，写死意味着每加一篇都要改 CSS。
   用 var(--i) 之后新增条目自动获得正确延迟，零维护。

   封顶 320ms：一组 30 个元素按每项 40ms 排下去，
   最后一个要等 1.2 秒才出现，用户会以为页面卡了。
   封顶后错落感保留在前几个元素上（人眼只能分辨前几下），
   同时保证任何长度的列表都在 1/3 秒内全部启动。

   ⚠️ 本块必须排在所有「组件级过渡规则」之后，原因见第 2 小节的注释。
*/

/* --- 2.1 组件自身的过渡（不含编排） ---
   这里全部使用 transition-property / -duration / -timing-function
   三个长写属性，刻意不用 transition 简写。

   为什么：transition 是简写属性，写它等于把 transition-delay 一起
   重置为 0s。卡片（.card/.post-card）同时又是 .reveal，
   组件规则一旦用简写，就会把编排层设好的错落延迟冲掉 ——
   表现为「延迟全是 0，动效齐刷刷一起出现」。

   这个坑很隐蔽：CSS 本身不报错，JS 测试全绿（逻辑是对的），
   只有真浏览器里量 computed style 才看得出 duration 变成了
   --dur-fast 而不是 --dur-base。

   注意卡片只列 border-color / box-shadow 两条轴，不列 transform ——
   transform 交给 .reveal（见 2.2），避免两条规则争同一属性轴。
   代价是悬停抬升跟着 240ms 而不是 140ms，慢约 100ms，
   换来的是「一套过渡规则只由一个选择器负责」，不再有覆盖关系。 */
.card,.post-card{
  transition-property:border-color,box-shadow;
  transition-duration:var(--dur-fast);
  transition-timing-function:var(--ease-out);
}
.card:hover,.post-card:hover{transform:translateY(calc(-1 * var(--shift-sm)))}

/* 按钮：按下时缩一点，给"点击已被接收"的即时反馈。
   这是唯一允许在 :active 上做缩放的组件 ——
   它必须是即时的，所以用最快的 --dur-fast，且在总开关下同样归零。 */
.btn,.prefs-toggle,.prefs-seg button,.prefs-actions button,
.pwa-update button,.pwa-ios-close{
  transition-property:transform,background,color,border-color;
  transition-duration:var(--dur-fast);
  transition-timing-function:var(--ease-out);
}
.btn:active,.prefs-toggle:active,.prefs-seg button:active,
.prefs-actions button:active{
  transform:translateY(0) scale(.97);
}

/* 导航项：下划线不必，靠背景色淡入即可，聚焦点应当留在内容上 */
.nav-links a{
  transition-property:color,background;
  transition-duration:var(--dur-fast);
  transition-timing-function:var(--ease-out);
}

/* 导航滚动加深：给背景与阴影一个短过渡，避免"啪"的一下变实 */
.nav{
  transition-property:background,box-shadow;
  transition-duration:var(--dur-base);
  transition-timing-function:var(--ease-out);
}

/* --- 2.2 编排规则（必须最后写） ---
   .reveal 的基础转录放在组件规则之后，是为了让它的
   transition / transition-delay 拥有最终的源顺序优势 ——
   同权重下后来者胜，这样卡片规则就再也冲不掉错落延迟了。

   代价：.card 的 duration 是 --dur-fast(140ms)，.reveal 是
   --dur-base(240ms)，同一个元素上两者都要用。
   解法是把它们放在两条不同的属性轴上 ——
   .reveal 管 opacity 与自身位移，.card 只管 border-color / box-shadow。
   transform 两边都在用，这里让 .reveal 赢（它是入场动画的主角），
   卡片悬停的微抬只在「已经入场完成」后才有视觉意义，不冲突。 */
.reveal{
  opacity:0;
  transform:translateY(var(--shift-lg));
  /* 入场过渡：覆盖 opacity 与 transform 两条轴 */
  transition-property:opacity,transform;
  transition-duration:var(--dur-base);
  transition-timing-function:var(--ease-out);
  /* 同组内按序号错开启动；--i 由 main.js 注入，缺省 0。
     单写 transition-delay 长写，不受上方任何简写影响。 */
  transition-delay:min(calc(var(--stagger-step) * var(--i,0)), 320ms);
  /* 卡片同时带 .card 时，悬停用的 border-color/box-shadow 过渡
     仍然生效（那两条走的是 .card 的 transition-property），
     而 transform 的过渡由本规则接管，两者互不覆盖。 */
}
.reveal.in{opacity:1;transform:none}

/* 静态列表（项目卡、联系卡）用 nth-child 给 --i，
   这样即便没有 JS 注入，它们也有错落效果。
   注意这里只赋值 --i，不直接写 transition-delay ——
   延迟的计算规则只保留在上面一处，避免两套机制打架。 */
.grid .reveal:nth-child(2){--i:1}
.grid .reveal:nth-child(3){--i:2}
.grid .reveal:nth-child(4){--i:3}
.grid .reveal:nth-child(5){--i:4}
.contact-grid .reveal:nth-child(2){--i:1}
.contact-grid .reveal:nth-child(3){--i:2}

/* 进场方向：默认从下方来。
   为什么统一改"自下方"而不是"自左/自右"：
   横向位移在窄屏上会制造横向溢出风险（浏览器会因此出滚动条），
   纵向位移没有这个问题，也更符合"页面是往下读的"直觉。 */
.reveal-slide{transform:translateY(var(--shift-md))}
.reveal-fade{transform:none}

/* 设置面板：从右上角轻微下拉展开。
   位移只用 --shift-sm，避免面板"飞"出来挡住导航。
   display:none 不能做过渡，所以关闭态用 visibility + opacity + transform，
   is-open 时再切回来。visibility 的过渡延迟等于时长，
   保证淡出动画播完才真正不可见、不可点击。 */
.prefs-panel{
  transition-property:opacity,transform,visibility;
  transition-duration:var(--dur-base);
  transition-timing-function:var(--ease-out);
  visibility:hidden;
  opacity:0;
  transform:translateY(calc(-1 * var(--shift-sm)));
}
.prefs-panel.is-open{
  display:block;
  visibility:visible;
  opacity:1;
  transform:none;
}

/* ---------- 第 2 层：降级层 ---------- */
/*
   七条路径，任何一条命中都必须保证内容可见。
   共同铁律：降级只允许"关掉动效"，绝不允许"关掉内容"。

   路径 1：系统级「减少动画」
   这是用户在操作系统里声明的可访问性偏好，优先级最高，
   连氛围背景的呼吸都要停 —— 前庭功能障碍用户对缓慢移动的背景
   反应比快速动画更强烈。 */
@media(prefers-reduced-motion:reduce){
  :root{
    --dur-fast:1ms;
    --dur-base:1ms;
    --dur-slow:1ms;
    --shift-sm:0px;
    --shift-md:0px;
    --shift-lg:0px;
  }
  .ocean-bg,.orb,.hero-card{animation:none!important}
  .reveal{opacity:1;transform:none;transition:none;transition-delay:0s}
  .card:hover,.post-card:hover{transform:none}
  .btn:active,.prefs-toggle:active,.prefs-seg button:active,
  .prefs-actions button:active{transform:none}
  html{scroll-behavior:auto}
}

/* 路径 2：用户开了系统级「增强对比度」
   这类用户往往同时失去对低透明度元素的辨识力。
   所以这里额外关掉 .reveal 的 opacity 过渡，只保留位移 ——
   位移不会让内容"暂时看不见"，透明度会。 */
@media(prefers-contrast:more){
  .reveal{opacity:1;transition:transform var(--dur-base) var(--ease-out);transition-delay:0s}
  .pwa-update,.pwa-ios-hint{backdrop-filter:none;-webkit-backdrop-filter:none}
}

/* 路径 3~5 在 main.js 里：无 IntersectionObserver、动效脚本抛异常、
   后台标签页暂停。这三条依赖运行时判断，CSS 无法表达。 */

/* 路径 6：脚本完全不可用。
   由 @media(scripting:none) 兜底，不依赖任何 JS。 */
@media(scripting:none){
  .reveal{opacity:1;transform:none;transition:none;transition-delay:0s}
  .prefs-panel{visibility:visible;opacity:1;transform:none}
  .nav{box-shadow:none}
}

/* 路径 7：浏览器不支持 min() 与自定义属性组合（如 Safari 14 及更早）。
   此时上面那条 transition-delay 会被整体丢弃 —— 没关系，
   丢的是启动延迟，只影响节奏，不影响内容显现，
   属于安全的退化方向（宁可没有错落，也不能不出现）。 */

/* ---------- 第 3 层：空间层 ---------- */
/*
   用「指针能不能悬停」分流，而不是用屏幕宽度。
   理由：翻盖笔记本、平板外接鼠标、带触控屏的桌面机都存在，
   宽度判断会把它们分错组。hover:hover 直接问的是能力本身。

   触屏设备没有悬停态，悬停位移永远不会触发，
   所以这里干脆不注册（省掉一次无用的样式计算）；
   同时把氛围运动降速，减少持续动画对电池与注意力的消耗。 */
@media(hover:hover){
  .card:hover,.post-card:hover{transform:translateY(calc(-1 * var(--shift-sm)))}
}
@media(hover:none){
  .card:hover,.post-card:hover{transform:none}
  :root{--dur-slow:2600ms}
}

/* 小屏位移减半：同样的 14px，在 375px 宽的屏幕上相对跨度大得多，
   看起来像"跳"。用媒体查询减半，比给每个组件写一套小屏样式简单得多。 */
@media(max-width:720px){
  :root{--shift-md:3px;--shift-lg:7px}
}

/* ---------- 第 4 层：总开关 ---------- */
/*
   用户在设置面板里关掉动效时，prefs.js 会写 html[data-motion="off"]。
   把三档时长与三档位移全部归零，等于一键摘除本块的所有效果 ——
   不需要给每个组件再写一遍覆盖规则。

   注意这里用 :root[data-motion="off"] 而不是 html[data-motion="off"]：
   变量定义在 :root 上，用同级选择器 + 更靠后的源顺序来覆盖，
   这样第 3 层媒体查询里的 --dur-slow 也会被一并压掉。

   总开关不隐藏任何内容：归零的是"变化过程"，不是"最终状态"。
   .reveal 在归零时长下会在同一帧内直接跳到 opacity:1，
   用户看到的是"内容本来就在那儿"，这正是期望的行为。 */
:root[data-motion="off"]{
  --dur-fast:1ms;
  --dur-base:1ms;
  --dur-slow:1ms;
  --shift-sm:0px;
  --shift-md:0px;
  --shift-lg:0px;
}
:root[data-motion="off"] .ocean-bg,
:root[data-motion="off"] .orb.a,
:root[data-motion="off"] .orb.b{animation:none}
:root[data-motion="off"] .hero-card{animation:none}
:root[data-motion="off"] .reveal{transition-delay:0s}
:root[data-motion="off"] .btn:active,
:root[data-motion="off"] .prefs-toggle:active,
:root[data-motion="off"] .prefs-seg button:active,
:root[data-motion="off"] .prefs-actions button:active{transform:none}
:root[data-motion="off"] .card:hover,
:root[data-motion="off"] .post-card:hover{transform:none}
