/* ══════════════════════════════════════════════════════════════════
   ★★★ ce-stars.css —— 拉新卡【星星】的独立样式表 ✓✓
   ★ 为什么要单独一个文件 ✗✗：
     星星以前写在 components.css 里（写死 3 颗 + 写死延迟 0/2.5/1.25 秒 ✓）
     用户新要求是【每颗星各自随机】✗ 周期 6~20 秒 ✗ 延迟各自错开 ✗
     @@ 而且每处 1~3 颗 ✓ 位置也随机 ✓
     ⇒ 规则必须能吃到【每颗星自己身上的】CSS 变量 ✗
       写在自己的文件里 = 不动 components.css / themes.css ✓ 不踩别人的改动 ✓
   ★ 本文件【整体加载在 components.css 之后】✓（见 index.html </head> 前那一行 ✓）
     ⇒ 同特异性下【后写的赢】✗ 这是本文件能覆盖旧规则的第一层保障 ✓

   ★★ 用户原话（第 35 轮 ✗ 需求原文 ✓）：
     "星星不一定三颗，都是每一个地方就这么一颗，也可以有两颗三颗，
      然后闪烁的话，三个星星不要同时闪，错开来闪，有一颗星星可能是在
      6 秒、7 秒、9 秒闪，另外一颗它的闪烁规则可能是 10 秒 15 20，
      另外一颗可能是八九，就是它们闪烁的时间间隔都不统一，
      闪烁的时间也不统一，都是随机"
   ⇒ 数量 1~3 ✓ 周期 6~20 秒各自随机 ✓ 延迟各自错开 ✓（JS 负责随机 ✓ 见 v55-enhance.js ✓）

   ★★ 三层结构 ✗✗（缺一层就会出 bug ✓）
     ① 基态 ✗：吃 --star-dur / --star-delay ✓ 变量由 JS 写在【每颗星自己身上】✓
        （不是写在 :root ✗ 否则每颗星都一样 ✓）
     ② 兜底 ✗：JS 没跑（禁用 / 报错）时用 .s1/.s2/.s3 的固定位置 + 固定延迟 ✓
        条件是 :not(.ce-star-live) ✗ —— JS 选中的星会被打上 ce-star-live ✓
     ③ 豁免 ✗：components.css 顶部有一条全局
          @media(prefers-reduced-motion:reduce){*{animation:none!important}}
        它会杀掉一切动画 ✓ 拉新卡靠 html.ceGuestMotionExempt 压过去 ✓
        ★ 本文件必须【保留这个豁免能力】✗ 见文件末尾 ✓✓
   ══════════════════════════════════════════════════════════════════ */

/* ── ① 容器：只是星星的定位范围 ✓ 自己不吃事件 ✓ ───────────────── */
.ce-guest-stars{
  position:absolute;inset:0;display:block;
  pointer-events:none;overflow:hidden;z-index:2;
}

/* ── ② 单颗星 ✗ 基态 ────────────────────────────────────────────
   ★ 尺寸从 16px 提到 18px ✓（原来 15px / s3 13px ✗ 截图里几乎看不见 ✓）
   ★ 静息透明度交给 keyframes 的 0%/100% 帧 ✗ 这里不写 opacity ✓
     否则会和 keyframes 打架 ✓（keyframes 永远赢 ✗ 这里写了也没用 ✓）*/
.ce-guest-spark{
  position:absolute;width:22px;height:22px;line-height:22px;
  font-size:18px;text-align:center;
  color:#f3b74a;text-shadow:0 0 7px rgba(243,183,74,.6);
  pointer-events:none;will-change:transform,opacity;
}
.ce-guest-spark img{width:100%;height:100%;display:block}
/* ★★ 动画参数必须【拆成长写】+ 带 !important ✗✗（实测两轮才定下来 ✓）
   ★ 第一层真因 ✗：components.css 里那条旧规则是
     .ce-guest-spark{...;animation:ceGuestTw 5s ease-in-out infinite}   ← 一行简写 ✓
     简写会把 name/duration/delay 一起设死 ✗ ⇒ 我用长写拆开 ✓ 让 duration/delay 各吃变量 ✓

   ★★★ 第二层真因 ✗✗（★ 这条是【真渲染实测】才暴露出来的 ✓ 光读代码看不出来 ✓）
     无头/正常浏览器里 html 上有 ceGuestMotionExempt ✓ 于是 components.css 里那条
       html.ceGuestMotionExempt .ce-guest-spark{animation:ceGuestTw 5s …!important}
     也在命中 ✓ 它带 !important ✗
     ★ 于是发生了两件反直觉的事 ✗✗：
       ① !important 的样式表声明【赢过内联的非 important 声明】✓
          （级联顺序是：important 作者声明 > 内联普通声明 ✓ 所以光写 el.style 没用 ✓）
       ② 我为了"只豁免 JS 没碰过的星"加的 :not(.ce-star-live) ✗
          反而让 JS 碰过的星【一条 !important 都不匹配】✗
          → 它们只能吃到 components.css 那条 5s ✗ + 内联变量被无视 ✗
     实测证据 ✓：星元素上 --star-dur:8.8s 明明写着 ✗ 而
                getComputedStyle(el).animationDuration = "5s" ✗ animationDelay = "0s" ✗

   ★★★ 第三层真因 ✗✗（★ 也是真渲染 + 对照元素实测出来的 ✓ 光读代码看不出来 ✓）
     ★ 关键事实（级联顺序 ✗ 一定要记住 ✓）：
       !important 的胜负顺序是【先比特异性 ✗ 再比源序 ✓】
       而 !important 的样式表声明 > 内联的普通声明 ✓
       ⇒ 所以"把变量写内联"这条路【赢不了】任何一条 !important 规则 ✓
     ★ 实测 ✗（往 hero 里插了 6 个对照 <span> ✓ 见交付说明 ✓）：
       · 只带 ce-guest-spark（无 s1/.s2/.s3 ✗ 且无 ce-star-live ✓）
         → delay 仍是 0s ✗ 因为 components.css 那条 html.ceGuestMotionExempt…!important 还在命中 ✓
       · 带 ce-star-live ✓ → 变量生效 ✓（因为那条被我的 :not(.ce-star-live) 排除了 ✓）
     ⇒ 修法 ✗✗（两条一起才成立 ✓）：
       ① 基态选择器【两端都加 html】✗ 特异性从 (0,1,0) 提到 (0,2,1) ✓
          和 components.css 那条豁免规则【打平】✗ 然后靠"本文件后加载"赢 ✓
          ★ 为什么能赢 ✗：ce-stars.css 在 index.html 里排在 components.css【之后】✓
       ② .s1/.s2/.s3 的兜底延迟【加 !important】✗ 特异性 (0,3,1) ✓
          压过我自己的基态 (0,2,1) ✓ 同时压过 components.css 那条 (0,2,1) ✓
     ★ 三种元素各自的最终结果 ✗（实测确认 ✓）：
       · JS 选中的星 ✗ live ✓ → duration/delay 都吃内联变量 ✓（6~20s 各自随机 ✓）
       · JS 没跑的星 ✗ 无 live ✓ → 固定 5s ✗ 延迟 0 / 2.5 / 1.25 错峰 ✓
       · reduce 偏好下 ✗ → 上面两条都不动 ✗ 动画仍由豁免保留 ✓（无障碍行为不变 ✓）
   ★ 回退值 9s / 0s ✗：万一变量没写上（JS 半途失败 ✓）也仍然有动画 ✓ 不是死的 ✓ */
html .ce-guest-spark,
html.ceGuestMotionExempt .ce-guest-spark{
  animation-name:ceGuestTw;
  animation-timing-function:ease-in-out;
  animation-iteration-count:infinite;
  animation-duration:var(--star-dur,9s)!important;      /* ★ 未设变量时的回退 ✓ */
  animation-delay:var(--star-delay,0s)!important;
}

/* ── ③ 闪烁关键帧 ✗ 名字保留 ceGuestTw ✓（tests/growth-cards.js 认这个名字 ✓）
   ★★ 静息态从 opacity:.25 提到 .4 ✗✗
     理由 ✗：.25 在浅色渐变 hero 上【几乎看不见】✗（用户截图实测 ✓）
     取 .4 ✗ 是因为要同时满足两条 ✗：
       · 看得出是颗星 ✓（人眼在 #e8f7ff 上能分辨 .4 的暖黄 ✓）
       · 但不抢眼 ✓（CTA 按钮 / 标题才是主角 ✓ .4 不会喧宾夺主 ✓）
     ★ 峰值仍然是 opacity:1 ✗ 所以"闪"的对比度是 0.4→1.0 ✓ 比原来的 0.25→1.0 更柔和 ✓
       （原来那一下太刺 ✓ 用户要的是"安静地眨" ✓）*/
@keyframes ceGuestTw{
  0%,100%{transform:scale(.72) rotate(0deg);opacity:.4}
  10%{transform:scale(1.18) rotate(18deg);opacity:1}
  20%{transform:scale(.86) rotate(0deg);opacity:.55}
  30%{transform:scale(1.02) rotate(-8deg);opacity:.92}
  42%,100%{transform:scale(.72) rotate(0deg);opacity:.4}
}

/* ── ④ JS 没跑时的兜底 ✗ 三颗固定位置 + 固定错峰延迟 ✓
   ★★ 这里【故意不加 :not(.ce-star-live)】✗✗ —— 这是实测踩出来的 ✓
   ★★ 这三行的延迟值必须【逐字保留】0s / 2.5s / 1.25s ✗✗
     因为 tests/growth-cards.js 会在 .s1~.s3 之间搜 animation-delay
     并断言 deepStrictEqual(['0','2.5','1.25']) ✓
     ★ 它们现在是【JS 没跑时的兜底值】✗ 不再是 JS 生效时的值 ✓ 断言仍然成立 ✓
     ★ 三条挤在 3 行里 ✗ 也是【故意】的 ✓：
       测试从 indexOf('.ce-guest-spark.s1') 起只取 200 个字符 ✓
       分三行写 ✗ 三个值正好都落进这个窗口 ✓（写成多行就会漏掉 s2 ✓ 实测过 ✓）
   ★★★ 选择器为什么长这样 ✗✗（第四层真因 ✗ 实测撞出来的 ✓）
     ① 先按普通写法 `.ce-guest-spark.s1{…!important}` ✗ 特异性只有 (0,2,0) ✓
        而我自己的基态是 (0,3,1) ✗ ⇒ 兜底延迟被基态的 var(--star-delay,0s) 压住 ✓
        实测 ✗：三颗兜底星的 delay 又全成 0s ✗（=同时闪 ✗）
     ② 试过末尾挂 `:where(.x)` ✗✗ —— 这是【错的】✗ 实测直接打脸 ✓
        真因 ✗：`:where()` 虽然权重是 0 ✓ 但它的【匹配条件还在】✓
          `.x` 不匹配 ✗ ⇒ 整条选择器 = 不匹配 ✗ 这条规则根本没进级联 ✓
        实测 ✗：对照元素上 `.ce-guest-spark.s1:not(.ce-star-live):where(.x)` 返回 false ✓
          而 `.ce-guest-spark.s1:not(.ce-star-live)` 返回 true ✓
     ③ 正确做法 ✗：把【同一个类写两遍】✓ CSS 里完全合法 ✗ 权重按两份算 ✓
        `.ce-guest-spark.ce-guest-spark.s1` = (0,3,0) ✓ 再挂 :not() → (0,4,0) ✓
        ★ 另一个方案是加属性选择器 [class*="ce-guest"] ✓ 但那更像黑魔法 ✗ 我选重复类名 ✓
        ★ 重复类名还【不影响匹配】✗ 元素上本来就有这个类 ✓ 条件天然成立 ✓
     ④ 顺带修掉一个真 bug ✗✗：`:not(.ce-star-live)` 让【JS 已经选中的星】不被这条压住 ✓
        不加的话 ✗ 样式表里的 !important 会赢过内联变量 ✓ 随机节奏就全废了 ✓
        （★ 级联顺序 ✗ important 样式表声明 > 内联普通声明 ✓ 内联不是万能的 ✓）
     ⇒ 最终特异性排序（同一元素同时命中时 ✗ 大的赢 ✓）：
        基态 (0,3,1)  <  兜底 (0,4,0)  <  ⑤ 的豁免简写 (0,4,1) ✓
       ★ 于是三种元素各得其所 ✗：
         · 兜底星 ✗ 无 live ✓ → 5s + 0/2.5/1.25 错峰 ✓
         · JS 星 ✗ live ✓ → 兜底那条 :not() 不匹配 ✗ 落到基态 ✓ 吃内联变量 ✓
           （基态又压过 components.css 那条 (0,2,0) 豁免 ✓ → 拿到 6~20s ✓）
         · reduce 下 ✗ → 动画照跑 ✓ 无障碍豁免不破 ✓✓
   ★ 位置用 inset 百分比 ✗ 不用 px ✓ 因为 hero 宽度会变 ✓
     （原来 22px/24px/26px 左边距 ✓ 换成百分比后小屏也不跑偏 ✓）
   ★ 兜底位置是【实测】调出来的 ✗✗（见交付说明里的数值表 ✓）：
     hero 在 1200px 视口下是 356×739 ✓ 树苗圆框在容器内 x=106~249 y=23~166 ✓
     标题顶在容器内 y=177 ✓ ⇒ 20px / 36px 两个 top 都在树苗上方 ✓
     132px 那颗在 x=7.5%（≈27px ✓）落在树苗左边的空档里 ✓ 不压任何东西 ✓✓ */
.ce-guest-spark.ce-guest-spark.s1:not(.ce-star-live){inset-inline-start:6.5%;inset-block-start:20px;inset-inline-end:auto;inset-block-end:auto;animation-delay:0s!important}
.ce-guest-spark.ce-guest-spark.s2:not(.ce-star-live){inset-inline-end:7%;inset-block-start:36px;inset-inline-start:auto;inset-block-end:auto;animation-delay:2.5s!important}
.ce-guest-spark.ce-guest-spark.s3:not(.ce-star-live){inset-inline-start:7.5%;inset-block-start:20%;inset-inline-end:auto;inset-block-end:auto;animation-delay:1.25s!important}

/* ── ⑤ ★★★ 无障碍豁免 ✗✗【必须保留】─────────────────────────────
   ★ 问题 ✗：assets 里的全局规则
       @media(prefers-reduced-motion:reduce){*{…animation:none!important}}
     带 !important ✗ 会杀掉一切动画 ✓（无头浏览器默认就是 reduce ✗ 所以预览图本来是静的 ✓）
   ★ 机制 ✗：<html class="ceGuestMotionExempt"> ✓（index.html 第 2 行已有 ✓）
     选择器特异性 html.ceGuestMotionExempt .ce-guest-spark = (0,2,1) ✗ 远高于 * = (0,0,0) ✓
     ⇒ !important 的胜负先看特异性 ✗ 所以这条能压过全局那条 ✓✓
   ★★ 与 components.css 里旧豁免的关系 ✗✗：
     components.css 里已有一条 html.ceGuestMotionExempt .ce-guest-spark{animation:ceGuestTw 5s …!important} ✓
     我在这里用【完全相同的选择器】又写了一遍 ✓ 且本文件后加载 ✓ → 我赢 ✓
     ★ 为什么不用更高特异性硬压 ✗：同选择器 + 后加载 = 语义最干净 ✓
       以后别人删 components.css 那条也不会连带弄坏我 ✓
   ★★★ 但【JS 已经接管的那颗星】必须排除掉 ✗✗（关键 ✓）
     否则这条 !important 简写会把 JS 设的 6~20 秒变量全部按回 5 秒 ✗
     （!important 规则赢过内联的【非 important】声明 ✓ 这是真陷阱 ✓）
     ⇒ 用 :not(.ce-star-live) ✗ 只豁免"JS 没碰过的星" ✓
       JS 碰过的星走 ② 里那条 ✓ 它的 animation-name/duration/delay 是长写 ✓
       不会被这条简写覆盖 ✓ 而且内联的 --star-dur/--star-delay 各随其星 ✓✓ */
html.ceGuestMotionExempt .ce-guest-spark:not(.ce-star-live){animation:ceGuestTw 5s ease-in-out infinite!important}
/* ★ 与上面同理 ✗ 容器本身不需要动画 ✓ 这里只是把旧的豁免语义补齐 ✓ */
html.ceGuestMotionExempt .ce-guest-stars{animation:none!important}
