/* ============================================================
   有牛 — 视觉系统
   一条墨线定义所有结构（1.5–2px），圆角只留给「贴纸」元素。
   田园感来自材料与手绘描边，不来自米色 + 衬线。
   ============================================================ */

:root{
  /* 材料 */
  --milk:    #FFFCF2;   /* 牛乳白 · 页面底 */
  --ink:     #17120D;   /* 墨线 · 描边与正文 */
  --pasture: #1E5B3A;   /* 牧草深绿 · 深色块 */
  --leaf:    #7FD14B;   /* 草叶亮绿 · 交互 */
  --hay:     #F0B429;   /* 干草黄 · 数字与高亮 */
  --mist:    #C6E0E5;   /* 晨雾蓝 · 天空 */
  --live:    #E2574C;   /* 在线信号 · 全站只用在「此刻在读」那个点上 */

  /* 派生 */
  --ink-60: color-mix(in srgb, var(--ink) 62%, var(--milk));
  --leaf-14: color-mix(in srgb, var(--leaf) 16%, var(--milk));
  --hay-30: color-mix(in srgb, var(--hay) 30%, var(--milk));

  /* 滚动条滑块。见下面「滚动条」那一节 —— 三个值分开是有原因的，
     别合成一个色：核心色和那圈光晕各自要对付不同的底。 */
  --sb:       var(--pasture);   /* 滑块芯 · 牧草深绿 */
  --sb-hover: var(--ink);       /* 按住/悬停 · 压到墨色 */
  --sb-halo:  var(--milk);      /* 滑块自带的一圈乳白描边 */

  --line: 2px;
  --hair: 1.5px;
  --r-pill: 999px;
  --r-sticker: 10px;

  /* 昼夜：默认值＝上午。daylight.js 会按北京时间把它们整组换掉，
     所以这里只作「JS 没跑起来」时的兜底，观感与以往一致。 */
  --sky-1:#bfe0ea; --sky-2:#dceff2; --sky-3:#fffcf2; --sky-4:#fbebc8;
  --field:#def1c7; --ground:#abe084;
  /* hero 的文字色分三档：无后缀=天顶、-mid=中部、-floor=地平线。
     三档都由昼夜引擎按各自那片天的亮度判黑白（见 daylight.js），
     solid 是配套的垫底色，永远跟字色反过来。

     注意 -floor 这一档有个例外：它**跟着天顶一起翻面**（一屏之内不许一半白一半黑），
     所以字色不再等于「地平线那片天该配的颜色」。--hero-edge-floor 才是那个值，
     专门留给红点描边用 —— 红点吃不到 text-shadow 光晕，是直接压在天空上的。 */
  --hero-line:#17120D;
  --hero-fg:#17120D;       --hero-fg-soft:#6b6b64;  --hero-solid:#fffcf2;
  --hero-fg-mid:#17120D;   --hero-fg-soft-mid:#544a3c; --hero-solid-mid:#fffdf6;
  --hero-fg-floor:#17120D; --hero-fg-soft-floor:#544a3c; --hero-solid-floor:#fdf3e6;
  --hero-edge-floor:#17120D;
  /* --hero-ring-floor 是统计行描边**专用**的一档色，夜里比 --hero-solid-floor 再深一档，
     拿「深度」换「宽度」用（详见下面 .stat b 那段注释）。
     这里必须是浅色（默认骨架是白天）：给墨字垫白边，加深会把白天打回去。 */
  --hero-ring-floor:#fffdf6;
  /* 统计行的描边不再用变量传宽度，改成 daylight.js 拨 html[data-hero-stroke]：
     变量里塞长度值时，那个 em 按「变量定义处」还是「使用处」的字号解析是有歧义的，
     实测外露的那点宽度被抗锯齿摊平，等于白描。写死 px 才控得住。 */
  --hero-solid:#FFFCF2; --cloud-fill:#FFFCF2;
  --hero-tuft:#64b046; --hero-blade:#529b43;
  --sun-fill:#F0B429; --sun-glow:#F0B429;
  --blob-a:#F0B429; --blob-b:#7FD14B;
  --star:0; --fly:0; --sun-a:1; --moon-a:0; --bird:1;
  --sun-dx:0vw; --sun-dy:0vh; --moon-dx:0vw; --moon-dy:0vh;

  --gutter: clamp(20px, 5vw, 56px);
  --maxw: 1180px;
  --ease: cubic-bezier(.2,.7,.2,1);

  --step-hero: clamp(2.5rem, 6.6vw, 5.6rem);
  --step-h2:   clamp(1.7rem, 3.4vw, 2.7rem);
  --step-h3:   clamp(1.16rem, 1.9vw, 1.6rem);
  --step-lead: clamp(1rem, 1.25vw, 1.14rem);
  --step-body: 1.0625rem;
  --step-meta: .8rem;

  --font-display: "Archivo", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
  --font-body: "LXGW WenKai", "Kaiti SC", "KaiTi", "STKaiti", "Songti SC", serif;
}

*,*::before,*::after{ box-sizing:border-box; }
html{ -webkit-text-size-adjust:100%; scroll-behavior:smooth; }
body{
  margin:0;
  background:var(--milk);
  color:var(--ink);
  font-family:var(--font-body);
  font-size:var(--step-body);
  line-height:1.95;
  letter-spacing:.015em;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
  /* 页脚贴底：整页竖着排，让中间那段（#main）把剩下的高度吃掉。
     内容长的时候一切照旧（main 撑得比视口高，页脚自然跟在后面）；
     内容短的时候 —— 空作品集、404、邻居页 —— 页脚就被顶到视口底边，
     而不是半页飘着、下面漏一截底色。
     高度用 dvh 而不是 svh：手机地址栏收起/展开时 dvh 会跟着改，
     页脚始终贴着**当下看得见的那条底边**；svh 只在工具栏全部展开时成立，
     一收起来页脚下边就漏出一道。 */
  min-height:100vh;
  min-height:100dvh;
  display:flex;
  flex-direction:column;
}
/* 页头是 position:fixed，本来就不占高度，这里不用管它。
   #main 会撑高；页脚固定按内容尺寸走，绝不参与压缩。 */
#main{ flex:1 0 auto; }
img,svg{ display:block; max-width:100%; }
h1,h2,h3,h4,h5,h6,p,figure,blockquote{ margin:0; }
a{ color:inherit; }
button,input{ font:inherit; color:inherit; }

:where(a,button,input,[tabindex]):focus-visible{
  outline:3px solid var(--ink);
  outline-offset:3px;
  border-radius:2px;
}

.sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}
.skip{
  position:fixed; top:8px; left:8px; z-index:200;
  transform:translateY(-160%);
  background:var(--ink); color:var(--milk);
  padding:10px 18px; border-radius:var(--r-pill);
  font-family:var(--font-display); font-weight:900; font-size:.85rem;
  text-decoration:none; transition:transform .2s var(--ease);
}
.skip:focus{ transform:none; }

.wrap{
  width:100%; max-width:var(--maxw);
  margin-inline:auto; padding-inline:var(--gutter);
}

/* ---------- 排版 ---------- */
.display{
  font-family:var(--font-display);
  font-weight:900;
  font-variant-numeric:tabular-nums;
  letter-spacing:-.005em;
  line-height:1.22;
}
h2.display{ font-size:var(--step-h2); }
.lead{
  font-size:var(--step-lead);
  line-height:2;
  color:var(--ink-60);
  max-width:34em;
}

/* ---------- 进度条 ---------- */
.progress{
  position:fixed; inset:0 auto auto 0; z-index:80;
  height:3px; width:0;
  background:var(--hay);
}
.progress__cap{
  position:absolute; right:0; top:-3px; width:9px; height:9px;
  background:var(--hay); border:var(--hair) solid var(--ink);
  border-radius:var(--r-pill);
  opacity:0; transition:opacity .3s var(--ease);
}
.is-scrolled .progress__cap{ opacity:1; }

/* ---------- 滚动条 ---------- */
/* 滑块＝一根圆头胶囊，外面套一圈乳白描边，宽度跟系统默认的 15px 对齐
   （写别的值都会让版面横向挪一下，因为 Windows 上 Chrome 的默认滚动条就是 15px）。

   那圈乳白描边不是装饰，是**为了让同一套规则能伺候背景天差地别的滚动容器**：
   后台侧栏是牧草深绿、正文代码块是近黑墨色、页面里还有各种深色贴纸卡片。
   纯一个色必然在某一种底上糊掉 —— 牧草深绿压在深绿侧栏上，整根滑块
   （15px 里唯一的可见部分）直接消失。加一圈乳白之后，
   乳白底上是「绿胶囊」、深绿和墨底上是「绿胶囊 + 乳白圈」，哪儿都立得住。
   所以 --sb 和 --sb-halo 不能合并成一个颜色。

   也别改成「透明边 + background-clip」那种留缝写法 —— 缝里透的是底色，
   深色底上照样看不见，绕回同一个问题。 */
::-webkit-scrollbar{ width:15px; height:15px; }
::-webkit-scrollbar-track{ background:transparent; }
::-webkit-scrollbar-corner{ background:transparent; }
::-webkit-scrollbar-thumb{
  /* 3px 描边把 15px 的槽收成一根 9px 的胶囊，两侧各留一道呼吸缝。
     描边再加粗（试过 4px）之后，深色底上"乳白圈 + 绿芯"里乳白会反客为主，
     一根滑块看着像一圈白描边 —— 这个宽度是两边都成立的折中。 */
  background:var(--sb);
  border:3px solid var(--sb-halo);
  border-radius:var(--r-pill);
  /* 悬停才加过渡：滚动条平时是静止的背景件，不该有常驻动效 */
  transition:background .15s var(--ease);
}
::-webkit-scrollbar-thumb:hover{ background:var(--sb-hover); }
::-webkit-scrollbar-thumb:active{ background:var(--sb-hover); }

/* 深绿面上把滑块芯换成草叶亮绿。
   牧草深绿压在牧草深绿上，芯整个融进底色，只剩那圈乳白描边可看 ——
   在后台侧栏（admin.css 的 .aside，全站唯一一处真的会出滚动条的深绿面）
   截下来就是这样：滑块读起来像一圈白描边，不像一根绿滑块。
   换亮绿之后芯才重新显形，而且 --leaf 本来就是站里指定的「交互」色。 */
.aside{ --sb:var(--leaf); --sb-hover:var(--milk); }

/* Firefox：没有 ::-webkit-scrollbar，只能走标准属性。
   那边画不出那圈乳白描边，所以深色面上会淡一些 —— 已知取舍。
   用 @supports 隔开，免得在 Chrome 里把上面那套 webkit 规则顶掉
   （scrollbar-color 一旦声明，Chromium 就整个忽略 ::-webkit-scrollbar）。 */
@supports not selector(::-webkit-scrollbar){
  *{ scrollbar-width:thin; scrollbar-color:var(--sb) transparent; }
}

/* ---------- 导航 ---------- */
.site-head{
  position:fixed; inset:0 0 auto 0; z-index:70;
  background:transparent;
  border-bottom:var(--line) solid transparent;
  transition:background .35s var(--ease), border-color .35s var(--ease);
}
.site-head__inner{
  display:flex; align-items:center; gap:28px;
  height:78px;
  transition:height .35s var(--ease);
}
.is-stuck .site-head__inner{ height:64px; }
.is-stuck{
  background:color-mix(in srgb, var(--milk) 88%, transparent);
  backdrop-filter:blur(10px) saturate(1.2);
  border-bottom-color:var(--ink);
}

/* 首屏页头是**透明**的，垫在它下面的是会随昼夜变深的天空；
   而导航文字原本继承 body 那份固定的深墨色，白天没事，
   一到夜里就是深字压深天 —— 实测只有 1.46:1，整条导航等于隐形。
   这里改吃 --hero-fg：昼夜引擎按「天顶亮度」判黑白，
   而页头正好压在天顶上，判据和位置是对得上的。
   滚下去以后 .is-stuck 给了奶油实底，就回到原来的深墨色。 */
.site-head:not(.is-stuck) .nav a,
.site-head:not(.is-stuck) .brand__word{ color:var(--hero-fg); }
.site-head:not(.is-stuck) .nav a[aria-current]::after{ background:currentColor; }

.brand{
  display:flex; align-items:center; gap:11px;
  text-decoration:none; margin-right:auto;
}
.brand__mark{ width:42px; height:42px; object-fit:contain; transition:width .35s var(--ease), height .35s var(--ease), transform .4s var(--ease); }
.is-stuck .brand__mark{ width:34px; height:34px; }
.brand:hover .brand__mark{ transform:rotate(-5deg) scale(1.04); }
.brand__word{
  font-family:var(--font-display); font-weight:900;
  font-size:1.42rem; letter-spacing:.02em; line-height:1;
  padding-bottom:3px;
}
.brand__word s{
  text-decoration:none; display:block;
  width:100%; height:3px; background:var(--leaf);
  transform:scaleX(0); transform-origin:left;
  transition:transform .35s var(--ease);
  margin-top:2px;
}
.brand:hover .brand__word s{ transform:scaleX(1); }

.nav{ display:flex; align-items:center; gap:30px; }
.nav a{
  text-decoration:none; font-size:.98rem;
  position:relative; padding-bottom:4px;
}
.nav a::after{
  content:""; position:absolute; left:0; right:0; bottom:0;
  height:2px; background:var(--leaf);
  transform:scaleX(0); transform-origin:left;
  transition:transform .3s var(--ease);
}
.nav a:hover::after{ transform:scaleX(1); }
.nav a[aria-current]{ font-weight:700; }
.nav a[aria-current]::after{ background:var(--ink); transform:scaleX(1); }

/* 「文章」是个两级入口：主项直接去 /all，归档和专题挂在下拉里。
   不用 JS —— :hover 和 :focus-within 就能覆盖鼠标和键盘两种走法。
   注意下面一律用 `.nav .nav__sub` 两段式：`.nav a` 是 (0,1,1)，
   单写 `.nav__sub` 是 (0,1,0)，会被前者压住 —— position:absolute 当场失效，
   下拉变成导航里挤着的第二项。这个坑踩过一次。 */
.nav__group{ position:relative; display:flex; }
/* 这块透明的 12px 是给鼠标留的过道：主项和下拉之间有空隙，
   没有它，指针一路过就会掉出 :hover，菜单当场收起来。 */
.nav__group::after{ content:""; position:absolute; left:0; right:0; top:100%; height:12px; }

/* 面板是个竖排容器，子项是它里面的 <a>。
   （面板曾经"就是那一项"，只有一个归档时那样最省；两项之后必须拆开。） */
.nav .nav__sub{
  position:absolute; top:calc(100% + 12px); left:-14px; z-index:5;
  /* 关着的时候**不生成盒子**，不只是"看不见"。
     只靠 opacity:0 + visibility:hidden 会漏影：页头吸顶那层有
     backdrop-filter，Chromium 做背景模糊时会把这种"隐形"的后代也算进去，
     于是菜单明明关着，页头上却浮着面板的圆角、硬阴影和「归档」两个字。
     它还是**间歇性**的 —— 同一处连拍，有的出有的不出，最容易被当成截图噪点放过去。 */
  display:none; flex-direction:column; align-items:stretch;
  min-width:92px;
  padding:6px;
  /* 实底 + 墨线 + 硬阴影，跟站里其他「贴纸」一致。
     这里**不用 backdrop-filter**：Chromium 下 opacity:0 的元素上，
     背景模糊仍然会被画出来 —— 菜单关着，页头上却浮着一块方影。 */
  background:var(--milk);
  border:var(--line) solid var(--ink);
  border-radius:var(--r-sticker);
  box-shadow:4px 5px 0 var(--ink);
  /* 这里**故意没有淡入**：状态只有"关着不生成盒子"和"开着立刻出现"两种。
     试过给它一段 display 过渡（allow-discrete + @starting-style），
     结果是**被重绘一逼就进到"淡入到一半"**的状态：菜单没被碰过，
     画面上却挂着一个半透明的面板（就是那块幽灵方影，验活脚本里那条
     「关着时连盒子都不生成」守的就是它）。下拉要的是快，不是那 0.2 秒。 */
}

/* 面板自己也得接住那条过道：group 上那截只有主项那么宽，而面板比主项宽，
   指针斜着往下滑会从面板右边掉出 :hover、菜单半路收起。
   挂在面板上的这一段，宽度天然跟着面板走。 */
.nav .nav__sub::before{ content:""; position:absolute; left:0; right:0; top:-12px; height:12px; }

.nav__group:hover .nav__sub,
.nav__group:focus-within .nav__sub{ display:flex; }

/* 面板里的一项。`.nav a::after` 那根绿色下划线在这儿不合适（每行都来一根，
   两行就成了双下划线），改成整块底色。padding 也必须重给一遍：
   `.nav a` 的 padding-bottom:4px 是给下划线留的位置。 */
.nav .nav__sub a{
  padding:7px 12px;
  font-size:.9rem; white-space:nowrap;
  border-radius:calc(var(--r-sticker) - 4px);
  transition:background .13s var(--ease);
}
.nav .nav__sub a::after{ display:none; }
.nav .nav__sub a:hover{ background:color-mix(in srgb, var(--pasture) 14%, var(--milk)); }

/* 首屏页头是透明的，导航文字会跟着昼夜翻黑翻白；但下拉自带牛乳色实底，
   浅字压浅底会直接消失。面板**和面板里每一项**都要单独写：
   `.site-head:not(.is-stuck) .nav a` 是直接命中 <a> 的，继承来的颜色压不过它，
   漏掉里层那句就是"菜单一打开，字没了"。 */
.site-head:not(.is-stuck) .nav .nav__sub,
.site-head:not(.is-stuck) .nav .nav__sub a{ color:var(--ink); }

.btn{
  display:inline-flex; align-items:center; justify-content:center;
  padding:11px 24px;
  border:var(--line) solid var(--ink);
  border-radius:var(--r-pill);
  background:var(--hay);
  font-family:var(--font-display); font-weight:900; font-size:.88rem;
  letter-spacing:.04em;
  text-decoration:none; cursor:pointer;
  box-shadow:3px 3px 0 var(--ink);
  transition:transform .18s var(--ease), box-shadow .18s var(--ease);
}
.btn:hover{ transform:translate(-1px,-2px); box-shadow:5px 6px 0 var(--ink); }
.btn:active{ transform:translate(3px,3px); box-shadow:0 0 0 var(--ink); }
.btn--ghost{ background:var(--milk); }

.burger{
  display:none; width:44px; height:44px;
  align-items:center; justify-content:center;
  border:var(--line) solid var(--ink); border-radius:var(--r-pill);
  background:var(--milk); cursor:pointer;
}
.burger span{ position:relative; width:18px; height:2px; background:var(--ink); }
.burger span::before,.burger span::after{
  content:""; position:absolute; left:0; width:18px; height:2px; background:var(--ink);
  transition:transform .25s var(--ease);
}
.burger span::before{ top:-6px; } .burger span::after{ top:6px; }
.nav-open .burger span{ background:transparent; }
.nav-open .burger span::before{ transform:translateY(6px) rotate(45deg); }
.nav-open .burger span::after{ transform:translateY(-6px) rotate(-45deg); }

.head-cta{ display:inline-flex; }

/* ---------- 牧场 ---------- */
.hero{
  position:relative;
  isolation:isolate;
  overflow:hidden;
  min-height:100vh;
  min-height:100svh;
  color:var(--hero-fg);
  background:
    linear-gradient(180deg,
      var(--sky-1) 0%,
      var(--sky-2) 26%,
      var(--sky-3) 52%,
      var(--sky-4) 78%,
      var(--sky-4) 100%);
  transition:color 1.8s linear;
}
/* 底部这块 padding 不是留白，是**给统计那几行小字垫出来的安全区**：
   场景层（山、风车、谷仓）是贴着 hero 底边绝对定位的，
   内容流一长就会压上去。留多宽要看山有多高，两者得一起调 ——
   只把 padding 加大是没用的，那只把 hero 撑高，山跟着一起往下走。 */
.hero__inner{
  position:relative; z-index:6;
  padding-block:clamp(112px, 17vh, 176px) clamp(300px, 40vh, 420px);
}
.hero h1{
  font-size:var(--step-hero);
  line-height:1.24;
  max-width:15em;
}
.hero .lead{ color:var(--hero-fg-soft-mid); }
.hero__lead{ margin-top:clamp(20px,3vh,32px); }

/* 黄昏和夜里，天地两头的亮度是**反着走**的：天顶已经暗了，地平线还挂着橙红。
   同一段文字没法靠一个颜色同时看清，所以给字配一层跟字色反过来的光晕：
   深字发白光、浅字发暗光 —— 「深字永远压浅底、浅字永远压深底」的规矩不变。

   注意这里是**光晕**（text-shadow 贴着字形走），不是一块垫在文字下面的底色。
   早先那版用的是「一块模糊过的圆角矩形」，白天看不出来，
   可一到黄昏（实测 19 点最亮处与周围天空差 77/255）就成了一块糊在天上的灰蓝色方块，
   夜里也一样。光晕只跟着笔画扩散，不会留下任何矩形的边界。 */
.hero__lead{
  text-shadow:
    0 0 10px color-mix(in srgb, var(--hero-solid-mid) 94%, transparent),
    0 0 24px color-mix(in srgb, var(--hero-solid-mid) 80%, transparent),
    0 0 46px color-mix(in srgb, var(--hero-solid-mid) 52%, transparent);
}
.hero__stats{
  /* 夜里这行字要压在暖橙色的黄昏天上（底色亮度 0.4–0.6），白字想站住就得有底。
     给字加「底」试了两轮，都是错的：
       · 加强 text-shadow —— 它是「晕」，blur 让它在笔画外 2px 就衰减掉。
         加到四层，紧贴笔画那一像素也只从 rgb(149,129,129) 压到 rgb(145,127,129)。
       · 一块椭圆软雾（radial-gradient 渐隐）—— 底是压住了，
         可一到黄昏天上**能看出那个椭圆**，跟早年那块「糊在天上的方块」同一个毛病。

     最后用**描边**：它贴着字形走，不占任何额外面积，没有形状可露。
       · 描边色取 --hero-solid-floor —— 跟字色相反，白字配深边、墨字配浅边；
       · paint-order:stroke fill 让描边画在字形**外面**
         （默认是居中的，会吃掉一半字身，白字会被啃瘦）；
       · 开不开由 daylight.js 按「字色跟那片天够不够 4.5」算出来，
         落在 html[data-hero-stroke] 上（css 里两条属性选择器）。
         白天是 off，跟原来一模一样。

     外面那层轻 text-shadow 留着，只负责让字形边缘跟天空融得自然些，
     不承担对比度的责任。

     描边宽度**写死在各自的文字元素上**（见下面的 .stat b / .stat span），
     不走 em、也不走 CSS 变量 —— 两个坑都踩过：
       · em 是按「声明所在元素」的字号解析的，写在容器上就只剩 17px 的一点点，
         数字（34px）那一档会细到看不见；
       · 变量里塞长度值时，那个 em 到底按定义处还是使用处解析有歧义，
         实测控不准，外露的那点宽度会被抗锯齿摊平，等于白描。
     数字和标签各自一个值，反而清楚。 */
  text-shadow:
    0 0 10px color-mix(in srgb, var(--hero-solid-floor) 58%, transparent),
    0 0 24px color-mix(in srgb, var(--hero-solid-floor) 30%, transparent);
  display:flex; flex-wrap:wrap; gap:clamp(26px,4vw,54px);
  margin-top:clamp(30px,5vh,52px);
}
.stat{ min-width:104px; border-top:var(--line) solid var(--hero-line); padding-top:12px; }
.stat b{
  display:block;
  font-family:var(--font-display); font-weight:900;
  font-size:clamp(1.5rem,2.6vw,2.15rem);
  line-height:1.15; letter-spacing:-.01em;
  /* 字色取 -floor 这一档，而不是跟着 .hero 继承 --hero-fg（那是天顶色）。
     这一档在 daylight.js 里的翻面时机被改成**跟天顶同步**了，
     所以统计行和大标题永远同色、不会再出现「标题白了这排还黑着」。
     夜里它是白字，白字能压在暖橙色的黄昏天上，靠的是 --hero-solid-floor
     那层深色光晕（见 .hero__stats 的 text-shadow）。 */
  color:var(--hero-fg-floor);
}
.stat span{ display:block; font-size:var(--step-meta); color:var(--hero-fg-soft-floor); letter-spacing:.08em; }

/* 统计行的描边：只在「字色跟那片天的对比够不上 4.5」时才拨开（黄昏那几个时刻，
   见 daylight.js）。白天不描，跟原来一模一样。

   宽度是**写死的 px**，不是 em、也不走 CSS 变量：
     · 描边是居中压在字形边缘上的，一半会被填充盖住，露在外面的只有一半 ——
       所以 3px 的描边实际只给出 1.5px 的实边。给 1.9px 时外露不到 1px，
       1x 屏下被抗锯齿一摊就没了（实测紧贴笔画处仍是 rgb(172,147,143)，
       那是描边和天空的混合色，不是描边）。
     · 不用变量传长度，是因为变量里的 em 到底按哪处的字号解析有歧义，
       实测控不准。数字和标签各自一个值，反而清楚。

   为什么最后是「3px / 2px + 比垫底色更深的专用色」，绕了一大圈：

     · 起手是 6px/3px，对比度够（6.72 / 5.85），用户一句「边框太粗了」打回。
     · 直接调细到 4px/2px 会掉出去 —— 标签只剩 4.00:1。
       **瓶颈是标签不是数字**：数字哪怕 3px 都还有 7.05，标签 1.5px 只有 3.70。
       12.8px 的小字笔画本身就只有 1.5px 宽，描边一居中、抗锯齿一摊，
       底就混进天空里了。而它占字号的比例是 23%，比数字（18%）还粗 ——
       用户嘴里那句「粗」，多半说的是这排小字。
     · 转机是**拿颜色深度换宽度**：同样是 2px 的标签描边，用垫底色 #1c2a46
       只有 4.00:1，换成 #0e1626 就有 5.46:1。因为紧贴笔画采到的从来不是纯描边色，
       而是「描边 × 亮天空」的混合色 —— 描边越深，混完仍然越暗。
       于是深度换出宽度：数字 6→3、标签 3→2，全时段最低反而从 6.72 升到 7.64。
     · 加粗字重（600）试过，本以为能让笔画少吃抗锯齿的亏，实测更差（4.20 ✗），放弃。
     · 再深到 #080d18 收益见顶、再细到 2.5/1.5 掉到 3.7 —— 深度和宽度都到头了，
       2px 是标签的下限。

   描边色走 --hero-ring-floor（daylight.js 里单独一档），不直接用 --hero-solid-floor：
   加深只该发生在白字那一侧；白天墨字配的是 #fffdf6 的浅边，跟着加深会把白天打回去。 */
html[data-hero-stroke="on"] .stat b{
  -webkit-text-stroke-width:3px;
  -webkit-text-stroke-color:var(--hero-ring-floor);
  paint-order:stroke fill;
}
/* 标签要写 `.stat > span`（直接子元素），**不能**写 `.stat span`。
   「N 人正在牧场」那一项的数字是套在 <b> 里的 <span class="liveNum">，
   `.stat span` 会把它一起选中 —— 于是那个 34px 的数字拿到的是标签的宽度
   （一度只有兄弟的一半）：同一个字号、两道粗细。
   -webkit-text-stroke 是可继承属性，收窄之后 .liveNum 自然从 <b> 继承。 */
html[data-hero-stroke="on"] .stat > span{
  -webkit-text-stroke-width:2px;
  -webkit-text-stroke-color:var(--hero-ring-floor);
  paint-order:stroke fill;
}

/* 「人此刻在读」是这里唯一会自己变的数字。
   红点是全站唯一的 --live —— 它不是材料色，是「live」这个语义专用的信号色，
   出现的位置只有这一处，所以不会打散调色板。
   这一项的呼吸和跳动都走 CSS 动画，prefers-reduced-motion 里那条全局规则会一并关掉。 */
.stat--live b{ display:flex; align-items:center; gap:10px; }
/* 单独一颗红色小圆点是**立不住**的：实测它在正午的浅米色天上只有 1.84:1，
   在深夜的蓝天上 1.15:1，等于糊掉。所以外面套一圈描边，
   颜色取 --hero-edge-floor —— 也就是「地平线那片天该配的字色」（白天墨线、夜里亮边），
   描边因此永远和它压着的那片天反着来。

   这里**不能**用 --hero-fg-floor：那个已经改成跟天顶同步翻面了，
   而红点不在 .hero__stats 的 text-shadow 光晕保护范围内（光晕贴字形，::before 吃不到），
   是直接压在天空上的。跟着字色走的话，黄昏天上会配出「白字 + 白描边」，红点当场消失。

   外圈那层淡光晕也用同一个色，跟着一起翻，别用红色（浅天上会更糊）。 */
.stat--live b::before{
  content:""; flex:none;
  width:9px; height:9px; border-radius:50%;
  background:var(--live);
  box-shadow:0 0 0 1.5px var(--hero-edge-floor),
             0 0 0 5px color-mix(in srgb, var(--hero-edge-floor) 16%, transparent);
  animation:liveBreath 2.8s var(--ease) infinite;
}
/* 数字外面这层 span 会撞上 .stat span 的小灰字样式（那是给标签用的），
   这里把它改回继承外层数字的字体与颜色。 */
.stat--live b .liveNum{ display:inline-block; font:inherit; color:inherit; letter-spacing:inherit; }
.liveNum.is-fresh{ animation:liveTick .7s var(--ease); }

@keyframes liveBreath{
  0%,100%{
    box-shadow:0 0 0 1.5px var(--hero-edge-floor),
               0 0 0 5px color-mix(in srgb, var(--hero-edge-floor) 16%, transparent);
  }
  50%{
    box-shadow:0 0 0 1.5px var(--hero-edge-floor),
               0 0 0 9px color-mix(in srgb, var(--hero-edge-floor) 7%, transparent);
  }
}
@keyframes liveTick{
  from{ transform:translateY(-4px); opacity:.3; }
  to  { transform:none; opacity:1; }
}

/* 场景层 */
.hero__scene{ position:absolute; inset:0; z-index:1; overflow:hidden; }
.hero__scene > *{ position:absolute; will-change:transform,translate; }
/* 云/山/前景这三层是按 2400 宽的 viewBox 以 slice 铺满的：
   只要「元素高 < 宽 × viewBox高/宽」，纵向就会溢出，而 SVG 根元素默认 overflow:hidden
   —— 溢出那一截会被直接裁掉，宽屏或矮窗口下山层顶上的风车叶片、前景的栅栏都会被削平。
   这三层是背景布，多出来的部分本来就该照画，收边交给 .hero__scene。 */
.hero__clouds, .hero__hills, .hero__front{ overflow:visible; }
/* 换色不跳：所有会随昼夜变化的图形都补一段过渡 */
.hero__scene :where(path,ellipse,circle){ transition:fill 1.8s linear, stroke 1.8s linear; }
.hero__sun,.hero__moon,.hero__stars,.hero__birds,.hero__flies,.hero__blob{
  transition:opacity 1.8s linear;
}

.hero__blob{
  border-radius:50%; filter:blur(70px); opacity:.42;
  pointer-events:none;
}
.hero__blob--a{ top:-6%; left:4%; width:44vw; height:38vh; background:radial-gradient(closest-side,var(--blob-a),transparent 72%); animation:drift 26s ease-in-out infinite alternate; }
.hero__blob--b{ top:14%; right:-6%; width:36vw; height:36vh; background:radial-gradient(closest-side,var(--blob-b),transparent 74%); animation:drift 32s ease-in-out infinite alternate-reverse; }

/* —— 星空 —— */
.hero__stars{
  inset:-6% 0 auto 0; height:82%;
  opacity:var(--star,0);
  pointer-events:none;
}
.hero__star{
  position:absolute; border-radius:50%;
  background:#fff;
  opacity:.9;
  animation:twinkle 4s ease-in-out infinite;
}
/* 流星：亮头在前（西南）、拖尾甩向东北。
   位移两个轴都用 vw —— 斜率才是不随视口比例变化的定值 11/34，
   拖尾角度因此唯一确定（-17.9deg），不会再出现「斜着飞、尾巴横着」的错位。
   改位移就得同时改 rotate：rotate = -atan(纵向/横向)。 */
.hero__meteor{
  --shoot-x:-34vw;   /* 横向位移：向左 */
  --shoot-y:11vw;    /* 纵向位移：向下。与横向同单位，斜率才是常数 */
  position:absolute; width:2px; height:2px; border-radius:50%;
  background:#fff; opacity:0;
  rotate:-17.93deg;  /* = -atan(11/34)，与上面两个位移严格对应 */
  box-shadow:0 0 8px 2px rgba(198,218,255,.9);
}
.hero__meteor::after{                 /* 尾巴挂在亮头后面：向右上拖 */
  content:""; position:absolute; top:50%; left:1px;
  width:120px; height:1.6px; transform:translateY(-50%);
  background:linear-gradient(90deg, rgba(226,238,255,.85), rgba(226,238,255,0));
}
.hero__meteor--a{ left:60%; top:9%; animation:shoot 21s linear 5s infinite; }
.hero__meteor--b{ left:84%; top:19%; animation:shoot 29s linear 15s infinite; }

/* —— 日月：同一个位置交叉淡入淡出 —— */
/* 位移走 right/margin —— translate 留给滚动视差，两者不打架 */
.hero__sun{
  /* 往左让开导航右上角的「随机一篇」按钮，光晕不会糊到按钮上 */
  right:calc(21% - var(--sun-dx,0%));
  margin-top:var(--sun-dy,0vh);
  top:11%;
  width:clamp(74px,9vw,124px); aspect-ratio:1;
  border-radius:50%;
  background:var(--sun-fill);
  opacity:var(--sun-a,1);
  box-shadow:0 0 0 14px color-mix(in srgb,var(--sun-glow) 26%, transparent),
             0 0 90px 26px color-mix(in srgb,var(--sun-glow) 40%, transparent);
}
.hero__moon{
  right:calc(21% - var(--moon-dx,0%));
  margin-top:var(--moon-dy,0vh);
  top:11%;
  width:clamp(56px,6.8vw,94px); aspect-ratio:1;
  color:var(--sun-fill);
  opacity:var(--moon-a,0);
  filter:drop-shadow(0 0 24px color-mix(in srgb,var(--sun-glow) 70%, transparent));
}
.hero__moonDisc{ fill:currentColor; }

/* —— 云 —— */
.hero__clouds{ top:3%; left:0; width:100%; height:clamp(92px,15vh,164px); }
/* 低空那层用 bottom 定位（不是 top%）：山是贴底绝对定位的，
   云跟着地平线走，才能在各种窗口高度下都贴着远山，而不是飘到半空去。 */
.hero__clouds--low{
  top:auto; bottom:clamp(150px,28vh,420px);
  height:clamp(48px,6.5vh,92px);
  opacity:.5;
}
.hero__cloud{ fill:var(--cloud-fill); stroke:var(--hero-line); stroke-width:3; stroke-linejoin:round; opacity:.92; }
.hero__cloud--a{ animation:cloudA 74s linear infinite; }
.hero__cloud--b{ animation:cloudA 104s linear infinite reverse; }
.hero__cloud--c{ animation:cloudA 88s linear infinite; }

/* —— 飞鸟 —— */
.hero__birds{ inset:0; opacity:var(--bird,1); pointer-events:none; }
.hero__bird{
  position:absolute; width:clamp(30px,3.2vw,52px); height:auto;
  stroke:var(--hero-line); stroke-width:4; stroke-linecap:round; fill:none;
  transform-box:fill-box; transform-origin:center;
  animation:flap 1.15s ease-in-out infinite, skim 68s linear infinite;
}
.hero__bird--a{ left:100%; top:16%; animation-duration:1.15s, 66s; animation-delay:-.2s, -6s; }
.hero__bird--b{ left:100%; top:23%; width:clamp(22px,2.4vw,38px); animation-duration:1.35s, 84s; animation-delay:-.5s, -30s; }
.hero__bird--c{ left:100%; top:11%; width:clamp(18px,2vw,30px); animation-duration:1s, 104s; animation-delay:-.9s, -62s; }

/* —— 山 —— */
.hero__hills{ left:0; bottom:0; width:100%; height:clamp(260px,36vh,400px); }
.hero__far{ fill:var(--field); }
/* 远山两层：都靠 color-mix 把「高空的蓝」掺进草色里 —— 越远掺得越多、
   越接近天空的颜色，这就是空气透视。用 --sky-2（偏蓝的上层天）而不是
   --sky-4（偏暖的地平线色）来掺：正午两者的地平线色都是浅米黄，
   掺进去远山会直接消失在天空里；用上层天的蓝，一天到晚都咬得住。 */
.hero__range{ stroke:none; }                       /* 不加描边：轮廓一虚，距离就出来了 */
.hero__range--far{ fill:color-mix(in srgb, var(--sky-2) 78%, var(--field)); }
.hero__range--near{ fill:color-mix(in srgb, var(--sky-2) 34%, var(--field)); }
.hero__bush{ fill:color-mix(in srgb, var(--hero-tuft) 46%, var(--field)); stroke:var(--hero-line); stroke-linejoin:round; }
.hero__ridge{ fill:none; stroke:var(--hero-line); stroke-width:4; }
.hero__barn{ fill:var(--hero-solid); stroke:var(--hero-line); }
.hero__barn path:first-child{ fill:var(--sun-fill); }

/* 树与花用 <use> 引用 <defs>，fill/stroke 走在组上、由子元素继承 */
.hero__tree{
  fill:color-mix(in srgb, var(--hero-tuft) 62%, var(--field));
  stroke:var(--hero-line); stroke-linejoin:round;
}
.hero__flowers{ fill:var(--cloud-fill); stroke:var(--hero-line); }

.hero__millTower,.hero__millCap{ fill:var(--hero-solid); stroke:var(--hero-line); stroke-linejoin:round; }
.hero__vanes{ transform-box:fill-box; transform-origin:center; animation:millSpin 22s linear infinite; }
.hero__vanes path{ fill:var(--hero-solid); stroke:var(--hero-line); stroke-linejoin:round; }

/* 线条粗细不随画布缩放变化，任何屏宽下都是同一条墨线 */
.hero__cloud,.hero__ridge,.hero__barn path,.hero__fence path,
.hero__blade path,.hero__tuft path,.hero__mill path,
.hero__bank,.hero__furrow path,.hero__hay ellipse,.hero__hay path,
.hero__scare path,.hero__scare circle,
.hero__pip,.hero__bird,#ynTree path{
  vector-effect:non-scaling-stroke;
}

.hero__cow{
  right:clamp(6%, 13%, 18%); bottom:210px;
  width:clamp(230px, 26vw, 396px);
  animation:cowIn 1s var(--ease) both;
}
.hero__cow img{
  width:100%; height:auto;
  filter:drop-shadow(0 14px 16px rgba(0,0,0,.16));
  animation:bob 7s ease-in-out infinite;
}

/* —— 近景 —— */
.hero__front{ left:0; bottom:0; width:100%; height:clamp(200px,28vh,300px); }
.hero__ground{ fill:var(--ground); }
.hero__tuft{ stroke:var(--hero-tuft); fill:none; stroke-linecap:round; }
.hero__blade{ stroke:var(--hero-blade); stroke-width:5; stroke-linecap:round; fill:none; transform-box:fill-box; transform-origin:bottom center; animation:sway 4.6s ease-in-out infinite alternate; }
.hero__fence{ stroke:var(--hero-line); stroke-width:4; fill:var(--hero-solid); stroke-linejoin:round; }

/* 田垄与前景暗带：草地上的纵深 */
.hero__bank{ fill:color-mix(in srgb, var(--ground) 74%, var(--pasture)); stroke:none; }
.hero__furrow{ fill:none; stroke:color-mix(in srgb, var(--ground) 58%, var(--pasture)); stroke-linecap:round; }
.hero__pip{ fill:var(--cloud-fill); stroke:var(--hero-line); }
.hero__pip--gold{ fill:var(--sun-fill); }

/* 稻草人 */
.hero__scarePole{ fill:none; stroke:var(--hero-line); stroke-linecap:round; }
.hero__scareCoat{ fill:var(--hero-solid); stroke:var(--hero-line); stroke-linejoin:round; }
.hero__scareHead{ fill:var(--hero-solid); stroke:var(--hero-line); }
.hero__scareHat{ fill:var(--sun-fill); stroke:var(--hero-line); stroke-linejoin:round; }
.hero__scareEye{ fill:var(--hero-line); stroke:none; }

.hero__hay ellipse,.hero__hay path{ fill:var(--sun-fill); stroke:var(--hero-line); }
.hero__hay path{ fill:none; }

/* 土路已撤：在 1440×900 这类尺寸下它只露出一小段，读起来不是「路」而是一道浅色抹痕。
   纵向的纵深交给远山与树的疏密，那里更稳。 */
.hero__stone{ fill:color-mix(in srgb, var(--pasture) 42%, var(--ground)); stroke:var(--hero-line); stroke-linejoin:round; }

/* —— 萤火虫 —— */
.hero__flies{ inset:0; opacity:var(--fly,0); pointer-events:none; }
.hero__fly{
  position:absolute; width:7px; height:7px; border-radius:50%;
  background:#FFF0A8;
  box-shadow:0 0 14px 5px rgba(255,214,90,.85), 0 0 34px 12px rgba(255,190,60,.35);
  animation:roam 10s ease-in-out infinite alternate, fireGlow 3s ease-in-out infinite;
}


/* ---------- 木牌跑马灯 ---------- */
.band{
  background:var(--ink); color:var(--hay);
  border-top:var(--line) solid var(--ink);
  border-bottom:var(--line) solid var(--ink);
  padding:22px 0; overflow:hidden;
}
.marquee{ display:flex; width:max-content; animation:slide 36s linear infinite; }
.band:hover .marquee{ animation-play-state:paused; }
.marquee__group{ display:flex; align-items:center; gap:46px; padding-right:46px; }
.marquee__item{
  font-family:var(--font-display); font-weight:900;
  font-size:clamp(1.1rem,2vw,1.6rem);
  white-space:nowrap; letter-spacing:.02em;
}
.marquee__sep{ width:22px; height:auto; flex:none; }

/* ---------- 章节 ---------- */
.sec{ padding-block:clamp(76px,12vh,132px); }
.sec__head{
  display:flex; align-items:flex-end; justify-content:space-between;
  gap:24px; flex-wrap:wrap;
  padding-bottom:22px;
}
.sec__link{
  font-size:.94rem; text-underline-offset:5px; text-decoration-thickness:1.5px;
  transition:color .2s var(--ease);
}
.sec__link:hover{ color:var(--pasture); }

/* ---------- 文章 ---------- */
.posts{ border-bottom:var(--line) solid var(--ink); }
.post{
  position:relative; isolation:isolate;
  display:grid;
  grid-template-columns:100px minmax(0,1fr) 130px 92px;
  align-items:baseline;
  gap:24px;
  padding:27px 4px 29px;
  border-top:var(--line) solid var(--ink);
  text-decoration:none;
}
.post::before{
  content:""; position:absolute; inset:0 -14px; z-index:-1;
  background:var(--leaf-14);
  transform:scaleX(0); transform-origin:left center;
  transition:transform .42s var(--ease);
}
.post:hover::before,.post:focus-visible::before{ transform:scaleX(1); }
.post__date{
  font-size:var(--step-meta); color:var(--ink-60);
  letter-spacing:.1em; font-variant-numeric:tabular-nums;
  transition:transform .38s var(--ease), color .38s var(--ease);
}
.post__title{
  font-size:var(--step-h3); line-height:1.5; font-weight:400;
  transition:transform .38s var(--ease);
}
.post:hover .post__title{ transform:translateX(10px); }
.post:hover .post__date{ transform:translateX(6px); color:var(--pasture); }
.post__tag{
  justify-self:end;
  font-size:var(--step-meta); letter-spacing:.06em;
  border:var(--hair) solid var(--ink); border-radius:var(--r-pill);
  padding:2px 13px 3px; white-space:nowrap;
  transition:background .3s var(--ease);
}
.post:hover .post__tag{ background:var(--milk); }
.post__time{
  justify-self:end; text-align:right;
  font-size:var(--step-meta); color:var(--ink-60); white-space:nowrap;
}
.post__grass{
  position:absolute; left:-2px; bottom:4px; width:42px; height:auto;
  transform:scaleY(0); transform-origin:bottom center;
  transition:transform .38s var(--ease);
}
.post:hover .post__grass,.post:focus-visible .post__grass{ transform:scaleY(1); }

/* ---------- 关于 ---------- */
.about{
  background:var(--pasture); color:var(--milk);
  border-top:var(--line) solid var(--ink);
  border-bottom:var(--line) solid var(--ink);
  position:relative; overflow:hidden;
}
.about__grid{
  display:grid; grid-template-columns:minmax(0,320px) minmax(0,1fr);
  gap:clamp(36px,6vw,88px); align-items:center;
}
.about h2{ color:var(--milk); }
.about__sticker{
  border:var(--line) solid var(--ink); border-radius:var(--r-sticker);
  background:color-mix(in srgb,var(--milk) 94%, var(--leaf));
  padding:18px 18px 0; box-shadow:6px 6px 0 var(--ink);
}
.about__sticker img{ margin-inline:auto; width:100%; height:auto; }
.about p{ color:color-mix(in srgb,var(--milk) 82%, var(--pasture)); max-width:33em; }
.about p + p{ margin-top:1.15em; }
.about blockquote{
  margin-top:clamp(28px,4vh,42px);
  padding-left:22px;
  border-left:4px solid var(--hay);
  font-size:clamp(1.06rem,1.7vw,1.3rem);
  line-height:1.85; color:var(--hay); max-width:24em;
}
.about__sign{ margin-top:16px; font-size:var(--step-meta); letter-spacing:.08em; color:color-mix(in srgb,var(--milk) 62%, var(--pasture)); }

/* ---------- 归档 ---------- */
/* 年份是逐年往上堆的，站点刚开张时只有一年 —— 写死三列就会空出三分之二的版面。
   auto-fit 会把没用到的列折掉，卡片跟着收窄到 480px 而不是孤零零一条挂在那儿；
   等年份多起来还能自动换行，不用改代码。 */
.archive__grid{
  display:grid; grid-template-columns:repeat(auto-fit, minmax(min(100%,320px), 480px));
  gap:clamp(20px,4vw,52px); margin-top:8px;
}
.year{
  display:flex; align-items:baseline; justify-content:space-between; gap:16px;
  border-top:var(--line) solid var(--ink);
  padding:18px 2px 20px; text-decoration:none;
  transition:background .3s var(--ease), padding-left .3s var(--ease);
}
.year:hover{ background:var(--hay-30); padding-left:12px; }
.year b{ font-family:var(--font-display); font-weight:900; font-size:clamp(1.5rem,2.4vw,2rem); letter-spacing:-.01em; }
.year span{ font-size:var(--step-meta); color:var(--ink-60); }

/* ---------- 随机看一篇（原订阅区的位置） ---------- */
/* 底色和那条顶边沿用订阅区的写法：它是「正文翻完了、还有个尾巴」的收尾块，
   用一道粗线跟上面的归档隔开，整页最后一段才不散。 */
.rand{
  border-top:var(--line) solid var(--ink);
  background:
    radial-gradient(120% 90% at 88% 12%, color-mix(in srgb,var(--hay) 26%, transparent), transparent 62%),
    var(--milk);
}
.rand__grid{
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,420px);
  gap:clamp(30px,5vw,72px); align-items:center;
}
/* 比导航那个 .btn 大一号：它是整页最后一个动作，也是唯一一个「点了会走」的块 */
.rand__go{ gap:11px; padding:16px 32px; font-size:.96rem; }
.rand__go svg{ width:20px; height:20px; flex:none; }
.rand__note{
  margin-top:16px; font-size:var(--step-meta); line-height:1.9; color:var(--ink-60);
}
.rand__note a{ text-underline-offset:4px; text-decoration-thickness:1.5px; }

@media (max-width:820px){
  .rand__grid{ grid-template-columns:1fr; gap:24px; }
}
[hidden]{ display:none !important; }

/* ---------- 页脚 ---------- */
.site-foot{
  flex:none;                                /* 上面 body 是 flex 列：页脚不许被压扁 */
  background:var(--ink); color:color-mix(in srgb,var(--milk) 78%, var(--ink));
  padding-block:52px 34px;
}
.site-foot__row{
  display:flex; align-items:center; gap:20px; flex-wrap:wrap;
  padding-bottom:26px; border-bottom:1px solid color-mix(in srgb,var(--milk) 24%, var(--ink));
}
.site-foot__brand{ display:flex; align-items:center; gap:12px; margin-right:auto; }
.site-foot__brand svg{ width:40px; }
.site-foot__brand b{ font-family:var(--font-display); font-weight:900; font-size:1.2rem; color:var(--milk); }
.site-foot nav{ display:flex; gap:26px; flex-wrap:wrap; }
.site-foot a{ text-decoration:none; font-size:.94rem; transition:color .2s var(--ease); }
.site-foot a:hover{ color:var(--hay); }
.site-foot__fine{
  display:flex; justify-content:space-between; gap:18px; flex-wrap:wrap;
  margin-top:22px; font-size:var(--step-meta);
  color:color-mix(in srgb,var(--milk) 46%, var(--ink));
}

/* ---------- 写信给我（页脚那个会弹出邮箱的链接） ---------- */
/* 它在深色页脚里，旁边都是普通链接 —— 给它一点分量，
   让人一眼看出「这个点了有东西」，而不是又一个跳转。 */
.site-foot .foot-mail{
  padding:5px 13px;
  border-radius:var(--r-pill);
  background:color-mix(in srgb, var(--hay) 18%, transparent);
  color:var(--hay);
}
.site-foot .foot-mail:hover{ background:var(--hay); color:var(--ink); }

.mailPop{
  position:fixed; inset:0; z-index:90;      /* 压过头图(70)和菜单(80)，让给跳转链接(200) */
  display:grid; place-items:center;
  padding:22px;
}
/* 这一行必须写：上面的 display:grid 会盖掉 [hidden] 自带的 display:none，
   漏了它弹层从一开始就糊在页面上，而且是「什么都看得见但关不掉」。 */
.mailPop[hidden]{ display:none; }

.mailPop__back{
  position:absolute; inset:0;
  background:color-mix(in srgb, var(--ink) 56%, transparent);
  animation:mailFade .18s var(--ease) both;
}
.mailPop__card{
  position:relative;
  width:min(430px, 100%);
  padding:30px 30px 26px;
  background:var(--milk);
  border:var(--line) solid var(--ink);
  border-radius:var(--r-sticker);
  box-shadow:7px 7px 0 color-mix(in srgb, var(--ink) 32%, transparent);
  animation:mailRise .2s var(--ease) both;
}
.mailPop__x{
  position:absolute; top:9px; right:11px;
  display:flex; align-items:center; justify-content:center;
  width:32px; height:32px;
  border:0; border-radius:var(--r-pill);
  background:transparent; color:var(--ink-60);
  font-size:1.4rem; line-height:1; cursor:pointer;
  transition:background .18s var(--ease), color .18s var(--ease);
}
.mailPop__x:hover{ background:var(--hay-30); color:var(--ink); }

.mailPop__title{
  font-family:var(--font-display); font-weight:900;
  font-size:1.16rem; letter-spacing:.02em;
}
.mailPop__lead{ margin-top:8px; font-size:.88rem; line-height:1.8; color:var(--ink-60); }

/* 邮箱地址：整段可选。手机上长按一下就能全选复制，
   比按钮更好使 —— 有些浏览器压根不给页面剪贴板权限。 */
.mailPop__addr{
  margin-top:16px; padding:14px 16px;
  background:color-mix(in srgb, var(--hay) 15%, var(--milk));
  border:var(--hair) dashed color-mix(in srgb, var(--ink) 38%, var(--milk));
  border-radius:8px;
  font-family:ui-monospace, SFMono-Regular, "Cascadia Mono", Menlo, Consolas, monospace;
  font-size:.98rem; text-align:center;
  word-break:break-all;                 /* 长地址不许把卡片撑破 */
  user-select:all; -webkit-user-select:all;
  cursor:text;
}

.mailPop__acts{ display:flex; gap:10px; flex-wrap:wrap; margin-top:18px; }
.mailPop__acts .btn{ flex:1 1 auto; padding:10px 18px; font-size:.84rem; }
.mailPop__copy.is-done{ background:var(--leaf); }

.mailPop__note{
  margin-top:12px; min-height:1.15em;      /* 预留一行，出字时按钮不会跳 */
  font-size:var(--step-meta); line-height:1.7;
  color:var(--pasture);
}

@media (max-width:420px){
  .mailPop__card{ padding:26px 20px 22px; }
  .mailPop__acts{ flex-direction:column; }
}

@keyframes mailFade{ from{ opacity:0 } to{ opacity:1 } }
@keyframes mailRise{ from{ opacity:0; transform:translateY(12px) scale(.98) } to{ opacity:1; transform:none } }

/* ============================================================
   唱片机挂件（右下角那枚黑胶）
   ------------------------------------------------------------
   视觉上就三件东西：盘面、中心那张干草黄的标签、唱臂。
   不画机身、不画底座 —— 挂件只有 56px，画满了就成贴纸了。
   动的地方只有两处：盘面转（33⅓ 转 = 1.8 秒一圈，照真实转速来），
   唱臂落下来并随进度往内圈走。其余的都安静。
   ============================================================ */
.radio{
  position:fixed; z-index:60;      /* 压住正文，让给页头(70)、菜单(80)、弹层(90)、跳链(200) */
  right:clamp(14px,3vw,30px);
  bottom:calc(clamp(14px,3vw,30px) + env(safe-area-inset-bottom, 0px));
  display:flex;
  /* DOM 里是「面板 · 唱片」，顺着排就得到「面板在左、唱片在右」——
     挂件被 right 钉在右下角，所以**唱片永远待在角上不动**，面板往左长。
     （反着写 row-reverse 的话，展开时唱片会被面板顶着往左跳一格。） */
  flex-direction:row;
  align-items:flex-end;
  gap:12px;
  --prog:0;
}

/* ---------- 唱片 ---------- */
.radio__unit{ position:relative; flex:none; width:64px; height:64px; }
.radio__disc{
  width:64px; height:64px; padding:0;
  display:grid; place-items:center;
  border:var(--line) solid var(--ink); border-radius:50%;
  background:var(--ink);
  cursor:pointer;
  box-shadow:3px 3px 0 var(--ink);
  transition:transform .16s var(--ease), box-shadow .16s var(--ease);
}
.radio__disc:hover{ transform:translate(-1px,-1px); box-shadow:4px 4px 0 var(--ink); }
/* 沟槽用 repeating-radial-gradient 叠出来：不引图片，缩放不糊 */
.radio__platter{
  width:100%; height:100%; border-radius:50%;
  display:grid; place-items:center;
  background:
    repeating-radial-gradient(circle at 50% 50%,
      rgba(255,255,255,.085) 0 1px, transparent 1px 3.5px),
    radial-gradient(circle at 50% 50%, #241c15 0 34%, #17120D 34% 100%);
}
.radio__label{
  width:34%; height:34%;
  position:relative;
  background:var(--hay);
  border:var(--hair) solid var(--ink);
  border-radius:50%;
}
.radio__label::after{                 /* 中心孔 */
  content:''; position:absolute; left:50%; top:50%;
  width:4px; height:4px; margin:-2px 0 0 -2px;
  background:var(--ink); border-radius:50%;
}
@keyframes radioSpin{ to{ transform:rotate(360deg) } }
.radio.is-playing .radio__platter{ animation:radioSpin 1.8s linear infinite; }

/* ---------- 唱臂 ---------- */
/* 支点在唱片右上角外一点：抬着的时候朝外撇（-24deg），落下来才是压在盘面上。
   --prog 是进度（0–1），radio.js 每帧写进来 —— 播到越靠后，唱头越往里圈走。
   臂和唱头都是「奶白 + 墨边」两色：单用墨色的话，它一压到墨色盘面上就没了 ——
   第一次就是这么做的，放大一看唱头整块糊在盘面上，像块脏东西。 */
.radio__arm{
  position:absolute; z-index:2; pointer-events:none;
  right:-3px; top:-5px;
  width:2px; height:36px;
  background:var(--milk);
  border:1px solid var(--ink);
  border-radius:1px;
  transform-origin:1px 5px;
  transform:rotate(-24deg);
  transition:transform .55s var(--ease);
}
.radio__arm::before{                  /* 唱头 */
  content:''; position:absolute; left:-2.5px; bottom:-4px;
  width:7px; height:9px; border-radius:2px;
  background:var(--milk); border:var(--hair) solid var(--ink);
}
.radio__arm::after{                   /* 转轴帽 */
  content:''; position:absolute; left:-3px; top:-3px;
  width:9px; height:9px; border-radius:50%;
  background:var(--milk); border:var(--hair) solid var(--ink);
}
.radio.is-playing .radio__arm{ transform:rotate(calc(26deg + var(--prog) * 12deg)); }

/* ---------- 面板 ---------- */
.radio__panel{
  width:min(272px, calc(100vw - 108px));   /* 108px = 唱片64 + 间距12 + 两边留白32 */
  padding:12px 14px 12px;
  background:var(--milk);
  border:var(--line) solid var(--ink);
  border-radius:var(--r-sticker);
  box-shadow:4px 4px 0 var(--ink);
  animation:radioRise .22s var(--ease) both;
}
.radio__top{ display:flex; align-items:center; gap:8px; justify-content:space-between; }
.radio__meta{
  margin:0;
  font-size:var(--step-meta); letter-spacing:.08em;
  color:var(--ink-60); font-variant-numeric:tabular-nums;
}
.radio__x{
  flex:none; width:22px; height:22px; padding:0;
  display:grid; place-items:center;
  background:none; border:0; border-radius:50%;
  font-size:1.1rem; line-height:1; color:var(--ink-60);
  cursor:pointer; transition:color .16s var(--ease), background .16s var(--ease);
}
.radio__x:hover{ background:var(--hay-30); color:var(--ink); }

.radio__title{
  margin:5px 0 7px;
  font-family:var(--font-display); font-weight:900;
  font-size:1rem; line-height:1.4; letter-spacing:-.01em;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.radio__row{ display:flex; align-items:center; gap:10px; }
.radio__time{
  flex:none;
  font-size:.78rem; color:var(--ink-60);
  font-variant-numeric:tabular-nums; letter-spacing:.02em;
}

.radio__bar{
  position:relative; flex:1; height:20px;
  display:flex; align-items:center;
  cursor:pointer;
}
.radio__bar::before{
  content:''; position:absolute; left:0; right:0; height:3px;
  background:color-mix(in srgb, var(--ink) 22%, var(--milk));
  border-radius:var(--r-pill);
}
.radio__fill{
  position:absolute; left:0; height:3px; width:0;
  background:var(--hay); border-radius:var(--r-pill);
}
.radio__knob{
  position:absolute; left:0; width:12px; height:12px; margin-left:-6px;
  background:var(--milk);
  border:var(--hair) solid var(--ink); border-radius:50%;
  transition:transform .12s var(--ease);
}
.radio__bar:hover .radio__knob{ transform:scale(1.16); }

.radio__acts{ display:flex; align-items:center; justify-content:center; gap:10px; margin-top:10px; }
.radio__btn{
  width:34px; height:34px; padding:0;
  display:grid; place-items:center;
  background:var(--milk); color:var(--ink);
  border:var(--line) solid var(--ink); border-radius:50%;
  cursor:pointer;
  box-shadow:2px 2px 0 var(--ink);
  transition:transform .14s var(--ease), box-shadow .14s var(--ease), background .14s var(--ease);
}
.radio__btn:hover{ background:var(--leaf); transform:translate(-1px,-1px); box-shadow:3px 3px 0 var(--ink); }
.radio__btn svg{ width:14px; height:14px; }
/* 只有一首（或已经在头/尾）时，按钮灰掉而不是点了没反应 */
.radio__btn[disabled]{ opacity:.34; cursor:not-allowed; box-shadow:none; }
.radio__btn[disabled]:hover{ background:var(--milk); transform:none; box-shadow:none; }
.radio__btn--main{ width:40px; height:40px; background:var(--leaf); }
.radio__btn--main svg{ width:16px; height:16px; }
.radio__pause{ display:none; }
.radio.is-playing .radio__play{ display:none; }
.radio.is-playing .radio__pause{ display:block; }

.radio__err{
  margin:9px 0 0;
  font-size:var(--step-meta); line-height:1.6; color:var(--live);
}

/* 挂件是 fixed 的，滚到页底时会压住页脚最下面那行小字（版权 / 页脚说明）——
   只在「内容栏已经贴到视口右边」的时候才会撞上：.wrap 是 1180 宽居中，
   视口比 1180 + 两边留白（2×56）还宽时，那行字的右端本来就在挂件左边。
   所以这里只在窄于那个宽度时，给页脚多留一截下边距，把它顶到挂件上方去。
   不这么做的话，1024 宽下滚到底，页脚那句「…和一头牛搭的，没别的」会被唱片盖掉两个字。 */
@media (max-width:1292px){
  body:has(.radio) .site-foot{ padding-bottom:calc(34px + 86px); }
}
.radio__err:empty{ display:none; }

@keyframes radioRise{ from{ opacity:0; transform:translateY(10px) } to{ opacity:1; transform:none } }

/* 转圈这件事是唯一「一直在动」的东西，尊重 prefers-reduced-motion。
   停转之后仍看得出在播：唱臂压着、按钮是暂停键。 */
@media (prefers-reduced-motion: reduce){
  .radio.is-playing .radio__platter{ animation:none; }
  .radio__arm{ transition:none; }
  .radio__panel{ animation:none; }
}

/* ---------- 光标光斑 ---------- */
.glow{
  position:fixed; z-index:2; top:0; left:0;
  width:560px; height:560px; margin:-280px 0 0 -280px;
  border-radius:50%; pointer-events:none; opacity:0;
  background:radial-gradient(closest-side, color-mix(in srgb,var(--hay) 42%, transparent), transparent 72%);
  transition:opacity .5s var(--ease);
  mix-blend-mode:multiply;
}

/* ---------- 入场 ---------- */
.js .reveal{ opacity:0; transform:translateY(24px); }
.js .reveal.is-in{
  opacity:1; transform:none;
  transition:opacity .75s ease, transform .85s var(--ease);
  transition-delay:var(--d,0ms);
}
.hero__inner > *{ animation:riseIn .9s var(--ease) both; }
.hero__inner > *:nth-child(1){ animation-delay:.34s; }
.hero__inner > *:nth-child(2){ animation-delay:.5s; }
.hero__inner > *:nth-child(3){ animation-delay:.64s; }
.hero .reveal{ opacity:1; transform:none; }        /* hero 用自身编排，不用滚动触发 */

.site-head__inner{ animation:dropIn .8s var(--ease) both; }
.hero__clouds{ animation:cloudsIn 1.5s var(--ease) both; }
.hero__hills{ animation:hillsIn 1.1s var(--ease) .28s both; }
.hero__front{ animation:frontIn 1.1s var(--ease) .46s both; }
/* 入场动画只碰 transform —— opacity 交给昼夜变量，否则动画结束会把透明度锁死 */
.hero__sun{ animation:sunIn 1.4s var(--ease) .1s backwards; }
.hero__moon{ animation:moonIn 1.6s var(--ease) .24s backwards; }

/* ---------- 关键帧 ---------- */
@keyframes riseIn{ from{ opacity:0; transform:translateY(30px); } to{ opacity:1; transform:none; } }
@keyframes dropIn{ from{ opacity:0; transform:translateY(-18px); } to{ opacity:1; transform:none; } }
@keyframes cloudsIn{ from{ opacity:0; transform:translateX(-34px); } to{ opacity:1; transform:none; } }
@keyframes hillsIn{ from{ transform:translateY(52px); opacity:0; } to{ transform:none; opacity:1; } }
@keyframes frontIn{ from{ transform:translateY(74px); opacity:0; } to{ transform:none; opacity:1; } }
@keyframes sunIn{ from{ transform:translateY(46px) scale(.7); } to{ transform:none; } }
@keyframes cowIn{
  0%{ opacity:0; transform:translateY(64px) scale(.94); }
  62%{ opacity:1; transform:translateY(-7px) scale(1.02); }
  100%{ opacity:1; transform:none; }
}
@keyframes sway{ from{ transform:skewX(-7deg); } to{ transform:skewX(7deg); } }
@keyframes bob{ 0%,100%{ transform:translateY(0); } 50%{ transform:translateY(-10px); } }
@keyframes drift{ from{ transform:translate3d(0,0,0) scale(1); } to{ transform:translate3d(6%, 4%, 0) scale(1.16); } }
@keyframes cloudA{ from{ transform:translateX(0); } to{ transform:translateX(140px); } }
@keyframes slide{ from{ transform:translateX(0); } to{ transform:translateX(-50%); } }

/* 昼夜新增 */
@keyframes moonIn{ from{ transform:translateY(38px) scale(.74) rotate(-14deg); } to{ transform:none; } }
@keyframes twinkle{ 0%,100%{ opacity:1; } 50%{ opacity:.22; } }
@keyframes shoot{
  0%{ opacity:0; translate:0 0; }
  3%{ opacity:1; }
  16%{ opacity:1; }
  26%{ opacity:0; translate:var(--shoot-x) var(--shoot-y); }
  100%{ opacity:0; translate:var(--shoot-x) var(--shoot-y); }
}
@keyframes flap{ 0%,100%{ transform:scaleY(1); } 50%{ transform:scaleY(.4); } }
@keyframes skim{ from{ translate:0 0; } to{ translate:-118vw 5vh; } }
@keyframes millSpin{ to{ transform:rotate(360deg); } }
@keyframes roam{
  from{ transform:translate3d(0,0,0); }
  to{ transform:translate3d(var(--fw,4vw), -2.4vh, 0); }
}
@keyframes fireGlow{ 0%,100%{ opacity:1; } 50%{ opacity:.18; } }

/* ---------- 响应式 ---------- */
@media (max-width:960px){
  .about__grid{ grid-template-columns:minmax(0,1fr); }
  .about__sticker{ max-width:300px; }
  .sub__grid{ grid-template-columns:minmax(0,1fr); }
}
@media (max-width:760px){
  body{ line-height:1.9; }
  .nav{
    position:absolute; inset:100% 0 auto 0;
    flex-direction:column; align-items:flex-start; gap:0;
    background:var(--milk);
    border-bottom:var(--line) solid var(--ink);
    padding:0 var(--gutter);
    max-height:0; overflow:hidden;
    transition:max-height .35s var(--ease), padding .35s var(--ease);
  }
  .nav-open .nav{ max-height:420px; padding:10px var(--gutter) 22px; }
  .nav a{ padding:14px 0; width:100%; border-bottom:var(--hair) solid color-mix(in srgb,var(--ink) 20%, var(--milk)); }
  .nav a::after{ display:none; }
  /* 窄屏没有「悬停」这回事：子项不再飘出去，直接摊在父项下面、缩进一级。 */
  .nav__group{ position:static; display:flex; flex-direction:column; align-items:stretch; width:100%; }
  .nav__group::after{ display:none; }
  /* 分隔线画在「组」上，不画在每一项上：这样「文章 + 归档 + 专题」是一块，
     线只出现在它和「作品集」之间，层级靠缩进和字号一眼分得开。 */
  .nav__group{ border-bottom:var(--hair) solid color-mix(in srgb,var(--ink) 20%, var(--milk)); }
  .nav .nav__group a{ border-bottom:0; }
  /* 抽屉里每一项都摊平了，面板退化成"缩进的一层"，贴纸造型整体撤掉。
     缩进从面板挪到每一项上（桌面是面板出内边距、项之间贴在一起）。 */
  .nav .nav__sub{
    position:static; display:block; min-width:0;
    opacity:1; transform:none;
    background:none;
    border:0; border-radius:0; box-shadow:none;
    padding:0 0 6px 20px;
  }
  /* 抽屉里没有那 12px 的空隙要接：过道留着就是一块绝对定位的空盒子，
     会飘在「文章」上面挡住点击。 */
  .nav .nav__sub::before{ display:none; }
  /* 每一项都要 display:block：抽屉下面板是 display:block，而 <a> 默认是行内，
     不写这句「归档 专题」会并排挤在同一行上，点按区域也只剩两个字那么宽。
     （桌面是 flex 竖排，所以只有窄屏会露这个相。） */
  .nav .nav__sub a{ display:block; padding:11px 0; font-size:.92rem; }
  /* 和上面那条「下拉固定吃墨色」同特异性、但在文件更后面，所以这里赢。
     里层同样得单独写一句：`.site-head:not(.is-stuck) .nav a` 直接命中 <a>。 */
  .nav .nav__sub,.site-head:not(.is-stuck) .nav .nav__sub,
  .nav .nav__sub a,.site-head:not(.is-stuck) .nav .nav__sub a{ color:var(--ink-60); }
  .burger{ display:inline-flex; }
  .head-cta{ display:none; }
  .nav .btn{ display:inline-flex; margin-top:16px; }

  .hero__inner{ padding-block:clamp(104px,15vh,140px) clamp(280px,38vh,340px); }
  .hero__cow{ right:3%; bottom:150px; width:clamp(178px,46vw,236px); }
  .hero__hills{ height:clamp(200px,27vh,270px); }
  .hero__front{ height:clamp(140px,18vh,200px); }
  /* 四项挤一行会把最长的那项（「天 · 从 … 算起」）压成三行、还压在牛头插画上。
     改成两列网格，四项正好两行两列，谁都不用为了迁就别人折行。 */
  .hero__stats{ display:grid; grid-template-columns:1fr 1fr; gap:16px 18px; width:auto; }
  .stat{ min-width:0; flex:none; }
  .hero__sun{ top:8%; width:clamp(54px,15vw,78px); right:calc(22% - var(--sun-dx,0%)); }
  .hero__moon{ top:8%; width:clamp(42px,12vw,62px); right:calc(22% - var(--moon-dx,0%)); }
  .hero__clouds{ top:5%; left:-22%; height:clamp(90px,14vh,140px); }
  .hero__barn{ transform:translateX(-170px); }
  .hero__bird--b{ display:none; }
  .hero__meteor::after{ width:74px; }
  .hero h1{ max-width:none; }
  .post{
    grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:"date tag" "title title";
    gap:8px 14px; padding:22px 4px 24px;
  }
  .post__date{ grid-area:date; }
  .post__title{ grid-area:title; }
  .post__tag{ grid-area:tag; }
  .post__time{ display:none; }
  .post__grass{ width:30px; }
  .archive__grid{ grid-template-columns:minmax(0,1fr); gap:0; }
  .band{ padding:18px 0; }
}

/* ---------- 尊重减弱动效 ---------- */
@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  *,*::before,*::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
  }
  .js .reveal{ opacity:1; transform:none; }
  .glow{ display:none; }
  .marquee{ animation:none; }
}