/* ==========================================================================
   细节层（2026-09-26 · 分支 readability）
   --------------------------------------------------------------------------
   这一层的每一个效果都挂在 <html> 的一个 class 上，可以单独开关：

     p-enter     入场：hero 分行走位、按钮最后到、卡片滚动到才浮现
     p-parallax  hero 图随滚动轻微位移（视差）
     p-breathe   hero 图缓慢推近再退回（像镜头呼吸）
     p-material  材质：颗粒感、卡片与按钮的多层阴影与内高光
     p-sheen     光泽：橙色渐变的细线、按钮划过的高光、卡片描边渐变
     p-tilt      卡片跟随鼠标：光晕随指针移动 + 轻微倾斜（追踪感）
     p-arrow     卡片右下角的箭头：悬停亮起，并朝指针方向微移

   默认全开；用地址上的查询参数切换，方便对比：
     /?polish=off                     全关（＝这一层完全不生效）
     /?polish=enter,sheen             只开这两项
     /?polish=material                只开材质

   三条纪律（写在最前面，改这个文件时别违反）：
     ① 系统开了「减少动态效果」时，动效整体不启用（在 index.html 的引导脚本里摘掉类）
     ② 只加在「看你」的地方（首页 hero、卡片、页脚），文档页保持素净
     ③ 不用会改变布局的属性做动画（只动 transform / opacity / 颜色 / 阴影），
        否则会掉帧，也会让文字在动画过程中重排
   ========================================================================== */

/* 颗粒纹理：一张 140×140 的 SVG 噪声，重复铺开。
   大块深色如果没有一点颗粒，看起来就是"塑料板"；加 4% 就够，多了会脏。 */
:root {
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ==========================================================================
   一、材质（p-material）
   ========================================================================== */

/* hero 上的颗粒：放在内容层里、压在最底下（z-index:-1 在 .hero-inner 的
   层叠上下文内，仍然高于 .hero::after 那层黑色遮罩，所以看得见） */
.p-material .hero-inner::before {
  content: "";
  position: absolute;
  inset: -2000px auto auto -2000px;
  width: 4000px; height: 4000px;
  background-image: var(--grain);
  opacity: .04;
  mix-blend-mode: soft-light;
  pointer-events: none;
  z-index: -1;
}

/* 页脚的颗粒：页脚也是整块深色，同样的问题 */
.p-material .site-footer { position: relative; }
.p-material .site-footer::before {
  content: "";
  position: absolute; inset: 0;
  background-image: var(--grain);
  opacity: .04;
  mix-blend-mode: soft-light;
  pointer-events: none;
}

/* 卡片的「材质」：不再用一层大阴影，而是三层叠起来 ——
   一道内高光（像纸的受光边）、一层贴近的浅影（像接触阴影）、一层远的柔影。
   三层一起才像实心物体；单独一层大阴影永远像贴纸。 */
.p-material .card {
  border-color: #ededed;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .9),
    0 1px 2px rgba(17, 24, 39, .04),
    0 10px 24px -16px rgba(17, 24, 39, .16);
}
.p-material .card:hover {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .9),
    0 2px 4px rgba(17, 24, 39, .05),
    0 18px 38px -18px rgba(17, 24, 39, .26),
    0 0 0 1px var(--brand-soft);
}

/* 按钮：白色药丸也要有厚度，不然是"纸片" */
.p-material .btn { box-shadow: inset 0 1px 0 rgba(255, 255, 255, .6), 0 1px 2px rgba(0, 0, 0, .06); }
.p-material .btn-light { box-shadow: inset 0 1px 0 #fff, 0 2px 6px rgba(0, 0, 0, .18), 0 14px 30px -14px rgba(0, 0, 0, .5); }
.p-material .btn-light:hover { box-shadow: inset 0 1px 0 #fff, 0 4px 10px rgba(0, 0, 0, .2), 0 20px 40px -16px rgba(0, 0, 0, .55); }

/* ==========================================================================
   二、光泽（p-sheen）
   ========================================================================== */

/* hero 与下方内容之间加一道橙色渐变的细线：不是分割线，是"光边"，
   从透明到品牌橙再到透明，比 1px 实线更有呼吸感。 */
/* ⚠ 这里不能用 .section:first-of-type —— hero 本身也是 <section>，
   :first-of-type 指的是「同类型里的第一个」，所以会选中 hero 而不是下面那块。
   用相邻兄弟选择器最直白：紧跟在 hero 后面的那个 section。 */
.p-sheen body.page-index .hero + .section { position: relative; }
.p-sheen body.page-index .hero + .section::before {
  content: "";
  position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--brand) 18%, var(--brand) 82%, transparent);
  opacity: .55;
}

/* 按钮划过的高光：hover 时一道窄窄的白色斜光从左侧扫过去。
   只给支持悬停的设备（触屏上永远不会触发，白占一次合成） */
@media (hover: hover) and (pointer: fine) {
  .p-sheen .btn { position: relative; overflow: hidden; }
  .p-sheen .btn::after {
    content: "";
    position: absolute; top: -60%; bottom: -60%; width: 38%;
    left: -60%;
    background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .55), transparent);
    transform: skewX(-18deg);
    pointer-events: none;
    transition: left .55s cubic-bezier(.2, .6, .25, 1);
  }
  .p-sheen .btn:hover::after { left: 130%; }
  .p-sheen .btn-outline::after,
  .p-sheen .btn-ghost::after { background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .28), transparent); }
}

/* 卡片 hover：描边不是整圈变橙，而是**上边缘**先亮起来（像被光照到），
   配合内容整体的微抬升，比整圈变色克制得多。 */
.p-sheen .card { position: relative; }
.p-sheen .card::after {
  content: "";
  position: absolute; inset: 0 18px auto 18px; height: 1px;
  background: linear-gradient(90deg, transparent, var(--brand) 50%, transparent);
  opacity: 0;
  transition: opacity .22s ease;
  pointer-events: none;
}
.p-sheen .card:hover::after { opacity: .9; }

/* 页脚的联系方式胶囊：hover 时透出一点品牌色的光 */
.p-sheen .contact-chip { transition: border-color .18s, box-shadow .18s, transform .18s; }
.p-sheen .contact-chip:hover { box-shadow: 0 0 0 1px var(--brand-soft), 0 6px 18px -12px rgba(255, 106, 0, .5); }

/* ==========================================================================
   三、入场（p-enter）
   ========================================================================== */

/* 标题是 <br> 分成三行的；把每行包进 .ln 之后就能分先后走位。
   ⚠ 用 inline-block 才能位移，但要保持右对齐（父级 text-align: right 仍然生效）。 */
.p-enter .hero h1 .ln { display: inline-block; }

.p-enter .hero h1 .ln,
.p-enter .hero-actions { animation: dt-rise .62s cubic-bezier(.2, .7, .3, 1) both; }
.p-enter .hero h1 .ln:nth-child(1) { animation-delay: .05s; }
.p-enter .hero h1 .ln:nth-child(3) { animation-delay: .14s; }
.p-enter .hero h1 .ln:nth-child(5) { animation-delay: .23s; }
.p-enter .hero-actions { animation-delay: .40s; }

@keyframes dt-rise {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}

/* 卡片：滚动到视野里才浮现（JS 加 .is-in）。没有 JS 时这些规则不生效，
   因为 p-enter 这个类是 JS 加上的 —— 见 index.html 的引导脚本。 */
/* 入场作用在 .tile（外层容器）上，倾斜也作用在它身上，两者一个用 translate、
   一个用 transform，互不打架；卡片自己被夹在中间，不参与这两件事。 */
.p-enter .grid-3 > .tile {
  opacity: 0;
  translate: 0 18px;
  transition: opacity .6s cubic-bezier(.2, .7, .3, 1), translate .6s cubic-bezier(.2, .7, .3, 1);
}
.p-enter .grid-3 > .tile.is-in { opacity: 1; translate: none; }
.p-enter .grid-3 > .tile:nth-child(2) { transition-delay: .08s; }
.p-enter .grid-3 > .tile:nth-child(3) { transition-delay: .16s; }
/* 已经在视野内的（比如从锚点直接打开）不要等：JS 会在同一帧加 .is-in */

/* ==========================================================================
   四、视差（p-parallax）
   ========================================================================== */

/* 只挪背景图的位置，不动任何会触发重排的属性。
   位移量由 JS 写进 --hero-shift（范围约 -40px ~ 0）。 */
.p-parallax .hero {
  background-position: center calc(50% + var(--hero-shift, 0px));
}

/* ==========================================================================
   五、hero 图片的缓慢推近（p-breathe）
   ========================================================================== */

/* 照片从 .hero 的 background 挪到 ::before 这一层，原因有两个：
   ① 直接在 .hero 上做 scale 会把标题和按钮一起放大；
   ② 视差改的是 background-position、呼吸改的是 transform，
      放在同一层上会互相打架；分成两层各管一个，就能同时工作。
   位移用 translate 这一独立属性（与 transform 叠加而非覆盖），
   所以 ::before 同时可以有"视差位移"和"呼吸缩放"。 */
.p-breathe .hero { background-image: none; }
.p-breathe .hero::before {
  content: "";
  position: absolute; inset: 0;
  background: var(--dark) url("/assets/img/hero.webp") center / cover no-repeat;
  translate: 0 var(--hero-shift, 0px);
  /* 极慢、幅度很小 —— 目标是"几乎察觉不到，但画面是活的"。
     28 秒一个来回，缩放范围 1.5%→7.5%（起点不取 1.00，避免起始一瞬间有跳变） */
  animation: dt-breathe 28s ease-in-out infinite alternate;
  transform-origin: 48% 45%;   /* 略偏左上：主体是桌子与显示器，别把视觉重心推跑 */
  will-change: transform;
}
@media (max-width: 760px) {
  .p-breathe .hero::before {
    background-image: url("/assets/img/hero-960.webp");
  }
}
@keyframes dt-breathe {
  from { transform: scale(1.015); }
  to   { transform: scale(1.075); }
}

/* 滚出视野就暂停：一个永不停歇的合成动画会一直吃电，而这时用户看不见它 */
.p-breathe .hero.is-paused::before { animation-play-state: paused; }

/* ==========================================================================
   六、卡片跟随鼠标（p-tilt）
   ========================================================================== */

/* 效果由两部分组成，缺一个都不像：
     ① **边框外沿的橙色光**跟着指针走 —— 做法是把一团橙色光斑放在卡片**背后**、
        比卡片大一圈，于是它从边缘漏出来，离指针最近的那条边最亮；
     ② 卡片朝指针方向轻微倾斜 —— rotateY 跟左右、rotateX 跟上下（各 6~8 度，多一度就晕）。

   ⚠ 倾斜写的是 transform，而"滚动入场"（p-enter）写的是 translate ——
   这两个是相互独立的属性，会叠加而不是互相覆盖。这正是当初把入场从
   transform: translateY 改成 translate 的原因：否则开一个就把另一个顶掉。 */
.p-tilt .grid-3 { perspective: 1000px; }   /* 透视放在父级：三张卡共用同一个视点，看起来才是一排 */

/* 外层容器：它承载"倾斜"和"橙光"，卡片只是它里面的内容 */
.p-tilt .grid-3 > .tile { position: relative; }

@media (hover: hover) and (pointer: fine) {
  .p-tilt .grid-3 > .tile {
    transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
    /* 这个 transition 会覆盖 p-enter 里给 .tile 的那份，所以三样都要写上：
       transform 是倾斜（要快），opacity/translate 是滚动入场（要慢） */
    transition:
      transform .18s cubic-bezier(.2, .7, .3, 1),
      opacity .6s cubic-bezier(.2, .7, .3, 1),
      translate .6s cubic-bezier(.2, .7, .3, 1);
    will-change: transform;
  }
  /* 卡片必须压在这层光之上，否则光会盖到卡片的白色表面上（踩过） */
  .p-tilt .grid-3 > .tile > .card { position: relative; z-index: 1; height: 100%; }

  /* 「升起一点点」：指针一进来，卡片先轻轻抬 5px，再跟着指针倾斜。
     抬升用 translate（与 tilt 的 transform 互不干扰），
     0.22s 的缓出——太快像被弹了一下，太慢就不像"反应"。 */
  .p-tilt .grid-3 > .tile > .card {
    transition: translate .22s cubic-bezier(.2, .7, .3, 1), border-color .18s, box-shadow .22s;
  }
  .p-tilt .grid-3 > .tile:hover > .card { translate: 0 -5px; }

  /* 橙色光：比卡片大 5px、模糊过，圆心跟着指针。
     它在卡片之外、卡片之下 —— 中间那部分被卡片白底挡住，
     只有超出边缘的一圈透出来，就是"边框上的橙色阴影"。 */
  .p-tilt .grid-3 > .tile::before {
    content: "";
    position: absolute; inset: -5px;
    border-radius: calc(var(--radius) + 5px);
    background: radial-gradient(
      260px circle at var(--mx, 50%) var(--my, 50%),
      rgba(255, 106, 0, .55),
      rgba(255, 106, 0, .22) 42%,
      transparent 64%);
    filter: blur(10px);
    opacity: 0;
    transition: opacity .24s ease;
    pointer-events: none;
    z-index: 0;
  }
  .p-tilt .grid-3 > .tile:hover::before { opacity: 1; }
  /* 边框本身也跟着暖一点：光从边上透出来时，边框不该还是冷灰色 */
  .p-tilt .grid-3 > .tile:hover > .card { border-color: rgba(255, 106, 0, .38); }
  /* 指针离开时把状态归零，否则卡片会停在倾斜的姿势上 */
  .p-tilt .grid-3 > .tile:not(:hover) { --rx: 0deg; --ry: 0deg; }
}

/* 演示模式：地址上加 ?tiltdemo=0.85,0.2 就让三张卡"假装鼠标停在那个位置"
   （0~1 的相对坐标）。用途有两个：一是截图/对比时不用真的挪鼠标，
   二是触屏设备上看不到悬停效果，用它可以看一眼长什么样。 */
.p-tilt .grid-3 > .tile.is-demo::before { opacity: 1; }
.p-tilt .grid-3 > .tile.is-demo > .card { translate: 0 -5px; }
.p-tilt .grid-3 > .tile.is-demo > .card { border-color: rgba(255, 106, 0, .38); }

/* ==========================================================================
   七、卡片右下角的箭头（p-arrow）
   ========================================================================== */

/* 平时是浅灰、安静地待在右下角；鼠标进到卡片里就变品牌橙，
   并朝指针方向偏一点点（最多 3px）—— 这就是"追踪感"在细节上的呼应。
   位移用 --ax/--ay（-1~1），由 detail.js 写入；没 JS 时它们取默认 0。 */
.p-arrow .grid-3 > .tile > .card .card-arrow {
  position: absolute; right: 18px; bottom: 13px;
  font-size: 15px; line-height: 1;
  color: #c6ccd6;
  transform: translate(calc(var(--ax, 0) * 3px), calc(var(--ay, 0) * 2.5px));
  transition: color .2s ease, transform .2s cubic-bezier(.2, .7, .3, 1);
  pointer-events: none;
}
.p-arrow .grid-3 > .tile:hover .card-arrow { color: var(--brand); }

/* ==========================================================================
   八、铺到其它页面（merch / freebies）
   ==========================================================================
   这两页的底子和首页不同：merch 是"两条可点的大卡片 + 一段正文"，
   freebies 是一篇长文档（八十多条资源）。所以不是照搬——

     merch    承接"手感"：抬起 + 朝指针倾斜 + 边框透出橙色。
              ⚠ 不放光斑：它们本身就是带边框的整行，光斑会显得脏。
     freebies 保持素净：只给页头一个入场，资源行给一点极轻的悬停反馈。
              文档页加动效只会让人觉得"这页怎么这么飘"。

   倾斜的挂载点用 [data-tilt]：首页是 .tile，merch 是每一条链接行。 */

.p-tilt [data-tilt] { position: relative; }
@media (hover: hover) and (pointer: fine) {
  .p-tilt [data-tilt] {
    transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
    transition: transform .18s cubic-bezier(.2, .7, .3, 1), translate .22s cubic-bezier(.2, .7, .3, 1), border-color .18s, box-shadow .22s;
  }
  .p-tilt [data-tilt]:hover { translate: 0 -4px; border-color: rgba(255, 106, 0, .34); }
  .p-tilt [data-tilt]:not(:hover) { --rx: 0deg; --ry: 0deg; }
}
/* merch 的箭头：跟着指针偏一点（只在这一页有 .lr-arrow） */
.p-arrow [data-tilt] .lr-arrow { transition: transform .2s cubic-bezier(.2, .7, .3, 1); }
.p-arrow [data-tilt]:hover .lr-arrow { transform: translateX(3px) translate(calc(var(--ax, 0) * 2px), 0); }

/* 两页的页头：进来时轻轻升上来，和首页同一套节奏 */
.p-enter .masthead .column > *,
.p-enter .page-head > .container > * { animation: dt-rise .62s cubic-bezier(.2, .7, .3, 1) both; }
.p-enter .masthead .column > *:nth-child(2),
.p-enter .page-head > .container > *:nth-child(2) { animation-delay: .09s; }
.p-enter .masthead .column > *:nth-child(3),
.p-enter .page-head > .container > *:nth-child(3) { animation-delay: .17s; }

/* freebies 的资源行：悬停时左边亮起一道极细的橙线。
   只是"知道鼠标在这儿"，不做位移——那页读起来要稳。 */
@media (hover: hover) and (pointer: fine) {
  .p-sheen .prose p.res { position: relative; transition: color .16s; }
  .p-sheen .prose p.res::before {
    content: "";
    position: absolute; left: -0.9em; top: .15em; bottom: .15em;
    width: 2px; border-radius: 2px;
    background: var(--brand);
    opacity: 0;
    transition: opacity .16s ease;
  }
  .p-sheen .prose p.res:hover::before { opacity: .75; }
}

/* ==========================================================================
   九、系统偏好：减少动态效果
   ========================================================================== */
/* 引导脚本已经会摘掉 p-enter / p-parallax；这里再兜一层，
   防止有人直接手写 class 或者浏览器不支持 matchMedia。 */
@media (prefers-reduced-motion: reduce) {
  .p-enter .hero h1 .ln,
  .p-enter .hero-actions,
  .p-enter .grid-3 > .card { animation: none !important; opacity: 1 !important; translate: none !important; }
  .p-parallax .hero { background-position: center; }
  .p-breathe .hero::before { animation: none; transform: none; }
  .p-sheen .btn::after { display: none; }
  .p-tilt .grid-3 > .tile { transform: none; }
  .p-tilt .grid-3 > .tile::before { display: none; }
  .p-arrow .card-arrow { transform: none; }
}
