/* ==========================================================
   前端精细化打磨层（样式）。版本仍是 2.9.15：这一层只改前端，后端和连接器不变。
   文件名里的 2916 和注释编号沿用《精细化打磨清单》，方便对照删改
   原则：看起来更安静，用起来更省事。
   · 只做"加法"，覆盖在已有样式之上，不改任何旧文件
   · 动效只用 transform / opacity，时长 120 / 200 / 320 三档，弹簧曲线沿用 motion.js 的 --sp-*
   · 所有新动效在"减弱动态效果"和站点动效开关关闭时自动静止
   配套行为见 js/core/v2916.js
   ========================================================== */
:root{
  --t-micro:120ms;              /* 按下、勾选这种一眼就过去的 */
  --e-out:cubic-bezier(.22,1,.36,1);
  --e-in:cubic-bezier(.4,0,1,1);
  /* 生产级曲线：无过冲。管理面板是高频工具，弹跳只留给"一个月才碰一次"的动作 */
  --e-ui:cubic-bezier(.2,0,0,1);
  --t-press:150ms;
  --edge:color-mix(in srgb,var(--ink) 13%,transparent);   /* 滚动边缘的浅阴影 */
  --glass-2:color-mix(in srgb,var(--surface) 86%,transparent);
}

/* ==========================================================
   一、滚动：让"还有内容"这件事自己说出来
   ========================================================== */

/* 1 表格横向还能滑时，两边浮出一层很浅的阴影；滑到头自动消失（纯 CSS，不用监听滚动） */
.card > .tbl-wrap{
  background:
    linear-gradient(var(--surface),var(--surface)) left center/16px 100% no-repeat local,
    linear-gradient(var(--surface),var(--surface)) right center/16px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%,var(--edge),transparent) left center/14px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%,var(--edge),transparent) right center/14px 100% no-repeat scroll;
}

/* 2 表格在屏幕里放得下时让表头吸顶（.fits 由脚本按实际宽度加；放不下时保持横向滚动，不吸顶） */
.page-main .card > .tbl-wrap.fits{overflow:visible;background:none}
.page-main .card > .tbl-wrap.fits .tbl th{box-shadow:0 1px 0 var(--line),0 6px 12px -12px var(--edge)}

/* 3 侧栏、浮层列表滚动到底之前，底部有一道极浅的渐隐 */
.side-scroll.can-scroll,.palette-list.can-scroll,.pop.can-scroll{
  -webkit-mask-image:linear-gradient(#000 calc(100% - 22px),transparent);
  mask-image:linear-gradient(#000 calc(100% - 22px),transparent);
}
.side-scroll.can-scroll.at-end,.palette-list.can-scroll.at-end,.pop.can-scroll.at-end{
  -webkit-mask-image:none;mask-image:none;
}

/* 4 回到顶部：往下滚过一屏半才出现，点一下平滑回顶 */
.to-top{
  position:fixed;z-index:68;right:24px;bottom:calc(92px + env(safe-area-inset-bottom,0px));
  width:38px;height:38px;display:grid;place-items:center;
  border:1px solid var(--line);border-radius:50%;background:var(--glass-2);color:var(--muted);
  backdrop-filter:blur(12px) saturate(1.4);-webkit-backdrop-filter:blur(12px) saturate(1.4);
  box-shadow:var(--shadow-float);
  opacity:0;transform:translateY(10px) scale(.86);pointer-events:none;
  transition:opacity .2s,transform .24s var(--e-ui),color .15s,border-color .15s;
}
.to-top svg{width:17px;height:17px}
.to-top.show{opacity:1;transform:none;pointer-events:auto}
.to-top:hover{color:var(--ink);border-color:var(--line-2)}
.to-top:active{transform:scale(.96);transition-duration:var(--t-press)}
body:not(:has(.ai-fab)) .to-top{bottom:calc(24px + env(safe-area-inset-bottom,0px))}
@media (max-width:900px){.to-top{right:16px;bottom:calc(78px + env(safe-area-inset-bottom,0px));width:40px;height:40px}}

/* 5 换页时顶上走一条两秒内结束的细线（接口慢时才看得见） */
.rt-bar{
  position:absolute;z-index:45;left:0;top:0;height:2px;width:100%;
  background:linear-gradient(90deg,color-mix(in srgb,var(--accent) 40%,transparent),var(--accent));
  transform:scaleX(0);transform-origin:left;opacity:0;pointer-events:none;
}
.rt-bar.run{opacity:1;transform:scaleX(.72);transition:transform 1.6s cubic-bezier(.1,.8,.2,1)}
.rt-bar.done{opacity:0;transform:scaleX(1);transition:transform .22s ease-out,opacity .3s .12s}

/* 6 主区滚动起来之后，手机顶栏下面才出现分隔阴影（不滚动时一条线都没有） */
@media (max-width:900px){
  .topbar{border-bottom-color:transparent;transition:border-color .2s,box-shadow .2s,transform .28s var(--e-out)}
  body.pg-scrolled .topbar{border-bottom-color:var(--line);box-shadow:0 6px 14px -14px var(--edge)}
  /* 7 手机上往下翻时顶栏让位，往上翻立刻回来 */
  body.bar-hide .topbar{transform:translateY(-100%)}
}

/* 8 滚动不再把整页带着晃（抽屉、对话框、浮层内部滚动到头不传给页面） */
.drawer-body,.modal-body,.pop,.palette-list,.rail,.page-main,.ai-body{overscroll-behavior:contain}

/* ==========================================================
   二、对话框：固定头尾、手机可下拉关掉
   ========================================================== */

/* 9 内容往下滚之后，标题下面才出现细线（和底部按钮的细线成对） */
.modal-head{border-bottom:1px solid transparent;transition:border-color .2s}
.modal.is-scroll:not(.is-top) .modal-head{border-bottom-color:var(--line)}

/* 10 手机底部面板：抓手更明显，按住可以往下拖，松手按位置决定关不关 */
@media (max-width:640px){
  .modal::before{width:44px;height:5px;background:var(--line-2);opacity:.9;margin:9px auto 2px;transition:width .2s,opacity .2s}
  .modal.is-drag::before{width:56px;opacity:1}
  .modal.is-drag{transition:none!important;animation:none!important}
  .modal.sheet-back{transition:transform var(--sp-sheet-ms) var(--sp-sheet)!important}
  .modal-wrap{touch-action:pan-y}
  /* 11 底部按钮贴着安全区，上面加一层同色渐隐，滚动的内容不会贴脸压在按钮上 */
  .modal-foot{position:relative;background:var(--surface)}
  .modal.is-scroll:not(.is-end) .modal-foot::before{
    content:"";position:absolute;left:0;right:0;top:-18px;height:18px;pointer-events:none;
    background:linear-gradient(transparent,var(--surface));
  }
}

/* 12 提交出错时输入区轻轻抖一下，顺带把错误滚到看得见的地方 */
@keyframes nudge{0%,100%{transform:none}22%{transform:translateX(-4px)}48%{transform:translateX(3px)}74%{transform:translateX(-2px)}}
.modal.is-err .modal-foot .err,.field.is-err .input,.field.is-err .select,.field.is-err .textarea{animation:nudge .34s var(--e-out)}
.field.is-err .input,.field.is-err .select,.field.is-err .textarea{border-color:var(--high)}

/* 13 按钮转圈时宽度不跳：文字留在原地，只是变淡 */
.btn.is-busy{position:relative;opacity:1;color:color-mix(in srgb,currentColor 42%,transparent)}
.btn.is-busy > *{opacity:.35}
.btn.is-busy::after{
  content:"";position:absolute;left:50%;top:50%;margin:-7px 0 0 -7px;width:14px;height:14px;
  border-radius:50%;border:2px solid var(--ink-2);border-right-color:transparent;animation:spin .7s linear infinite;
}
.btn.primary.is-busy::after,.btn.accent.is-busy::after,.btn.danger-fill.is-busy::after{border-color:#fff;border-right-color:transparent}

/* 14 做完一件事按钮短暂变成勾，不用等提示条 */
.btn.did{color:var(--ok)!important;border-color:color-mix(in srgb,var(--ok) 45%,var(--line))!important}
/* 图标交换用交叉淡入（0.25→1、模糊 4→0、300ms、无过冲），不用弹簧 */
.btn.did svg{animation:iconSwap .3s var(--e-ui)}
@keyframes iconSwap{from{transform:scale(.25);opacity:0;filter:blur(4px)}to{transform:none;opacity:1;filter:blur(0)}}
/* 从 scale(.4) 起步会"弹"得不自然；0.86 起步是自然的出现，不是弹出 */
@keyframes popIn{from{transform:scale(.86);opacity:0}to{transform:none;opacity:1}}

/* 15 多层浮层：第二层打开时，第一层也往后退一点 */
body.is-layered-2 .modal:not(:last-of-type),body.is-layered-2 .drawer{transform:scale(.976);filter:saturate(.92) brightness(.98)}

/* ==========================================================
   三、抽屉：右滑关掉、内容分段出现
   ========================================================== */
/* 16 抓着标题往右拖就能关，和手机上的返回手势一致 */
.drawer.is-drag{transition:none!important}
.drawer-head{cursor:grab;touch-action:pan-y}
.drawer.is-drag .drawer-head{cursor:grabbing}
.drawer::before{
  content:"";position:absolute;left:5px;top:50%;width:4px;height:42px;margin-top:-21px;border-radius:2px;
  background:var(--line);opacity:0;transition:opacity .2s;
}
@media (hover:hover){.drawer:hover::before{opacity:.8}}

/* 17 【已撤销】抽屉分节逐段进入。看一个玩家详情是每天几十次的动作，
   分段落位等于每次都要多等 0.11 秒；抽屉自己滑进来已经交代清楚了"这是新的一层"。 */

/* 18 手机上抽屉改成从底部上来的大面板，更接近系统的习惯 */
@media (max-width:640px){
  .drawer,.drawer.wide{
    top:auto;left:0;right:0;width:100%;height:min(92dvh,100%);max-height:92dvh;
    border-radius:18px 18px 0 0;transform:translateY(100%);
    transition:transform var(--sp-sheet-ms) var(--sp-sheet);
  }
  .drawer.show{transform:none}
  .drawer::after{content:"";position:absolute;left:50%;top:7px;width:44px;height:5px;margin-left:-22px;border-radius:3px;background:var(--line-2)}
  .drawer-head{padding-top:22px}
  .drawer-foot{padding-bottom:calc(14px + env(safe-area-inset-bottom,0px))}
  .drawer-foot .btn{min-height:44px;flex:1 1 0;justify-content:center}
}

/* 19 抽屉内容往下滚的时候，标题区压扁一点，给内容让地方 */
.drawer-head{transition:padding var(--sp-snappy-ms) var(--sp-snappy)}
.drawer-head h2{transition:font-size var(--sp-snappy-ms) var(--sp-snappy)}
.drawer.hd-min .drawer-head{padding-top:12px;padding-bottom:10px}
.drawer.hd-min .drawer-head h2{font-size:17px}
.drawer.hd-min .drawer-head .s{opacity:0;height:0;margin:0;overflow:hidden;transition:opacity .15s}

/* ==========================================================
   四、提示条：看得见剩多少时间，可以滑走
   ========================================================== */
/* 20 带"撤销"的提示条下面有一条倒计时，剩多久一眼看得出来 */
.toast{position:relative;overflow:hidden;touch-action:pan-y}
.toast .t-bar{
  position:absolute;left:0;right:0;bottom:0;height:2px;transform-origin:left;
  background:color-mix(in srgb,var(--surface) 50%,transparent);
  animation:tBar linear forwards;
}
.toast.err .t-bar{background:color-mix(in srgb,var(--high) 70%,transparent)}
@keyframes tBar{from{transform:scaleX(1)}to{transform:scaleX(0)}}
.toast:hover .t-bar{animation-play-state:paused}

/* 21 同一句提示反复出现时合并成一条，后面带个次数 */
.toast .t-n{
  flex:none;min-width:18px;height:18px;padding:0 5px;border-radius:9px;
  background:color-mix(in srgb,var(--surface) 22%,transparent);
  font-size:11px;line-height:18px;text-align:center;font-variant-numeric:tabular-nums;
}
.toast.bump{animation:bump .26s var(--e-ui)}
@keyframes bump{0%,100%{transform:none}40%{transform:scale(1.035)}}

/* 22 手机上提示条可以横着滑走，不必等它自己消失 */
.toast.is-swipe{transition:none!important}
.toast.swipe-back{transition:transform .26s var(--e-ui)!important}

/* 23 多条堆叠时，后面的往里缩一点点，层次清楚又不占地方；鼠标移上去全部恢复 */
.toasts .toast.show:nth-last-child(2){transform:scale(.985)}
.toasts .toast.show:nth-last-child(n+3){transform:scale(.97);opacity:.88}
.toasts:hover .toast.show{transform:none;opacity:1}

/* ==========================================================
   五、表单：少看说明，少点几下
   ========================================================== */
/* 24 聚焦时左边的标题跟着变色，长表单里一眼知道自己在填哪一项 */
.field:focus-within > label,.field:focus-within > .label{color:var(--ink);transition:color .15s}

/* 25 搜索框里有字时右边出现清除按钮，不用一直按退格 */
.input-ic{position:relative}
.input-ic .in-clear{
  position:absolute;right:6px;top:50%;width:22px;height:22px;margin-top:-11px;
  display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--faint);
  opacity:0;transform:scale(.86);pointer-events:none;transition:opacity .15s,transform .18s var(--e-ui),color .15s;
}
.input-ic .in-clear svg{width:13px;height:13px}
.input-ic.has-val .in-clear{opacity:1;transform:none;pointer-events:auto}
.input-ic .in-clear:hover{color:var(--ink);background:var(--sunk)}
.input-ic.has-val .input{padding-right:30px}
/* 没有 .input-ic 包着的输入框（实时对局里的几个），按钮由脚本按位置摆 */
.in-clear-host{position:relative}
.in-clear-host > .in-clear{position:absolute;width:22px;height:22px;display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--faint);transition:opacity .15s,transform .18s var(--e-ui),color .15s}
.in-clear-host > .in-clear:hover{color:var(--ink);background:var(--sunk)}

/* 26 多行输入框跟着内容长高，不用自己拖；到一定高度就改成内部滚动 */
.textarea.auto{resize:none;overflow-y:auto;transition:height var(--t-micro) linear}

/* 27 有字数上限的输入框，右下角安静地显示还能打多少（超了才变色） */
.ta-wrap{position:relative}
.ta-wrap .ta-n{
  position:absolute;right:10px;bottom:7px;font-size:11px;color:var(--faint);
  font-variant-numeric:tabular-nums;pointer-events:none;opacity:0;transition:opacity .2s,color .2s;
}
.ta-wrap:focus-within .ta-n{opacity:1}
.ta-wrap .ta-n.near{color:var(--mid);opacity:1}
.ta-wrap .ta-n.over{color:var(--high);opacity:1}

/* 28 密码框右边加一个"看一眼"的按钮 */
.pw-eye{
  position:absolute;right:6px;top:50%;width:26px;height:26px;margin-top:-13px;
  display:grid;place-items:center;border:0;border-radius:6px;background:transparent;color:var(--faint);
  transition:color .15s,background-color .15s;
}
.pw-eye:hover{color:var(--ink);background:var(--sunk)}
.pw-eye svg{width:15px;height:15px}
.pw-wrap{position:relative}

/* 29 开关拨动时轨道颜色先走、滑块后到，手感更"实" */
.switch i{transition:background-color .2s var(--e-out)}
.switch input:checked + i{background:var(--ok)}
.switch input:checked + i::after{box-shadow:0 1px 3px rgba(0,0,0,.3)}

/* 30 单选卡片选中时左上角出现一个勾，而不是只靠边框色 */
.role-card,.plan-edit{position:relative}
.role-card[aria-pressed="true"]::after,.role-card.on::after{
  content:"";position:absolute;right:10px;top:10px;width:16px;height:16px;border-radius:50%;
  background:var(--accent);
  -webkit-mask:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6 9 17l-5-5' fill='none' stroke='black' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'/></svg>") center/12px no-repeat,linear-gradient(#000,#000);
  -webkit-mask-composite:destination-out;
  mask:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6 9 17l-5-5' fill='none' stroke='black' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'/></svg>") center/12px no-repeat,linear-gradient(#000,#000);
  mask-composite:exclude;
  animation:popIn .26s var(--e-ui);
}

/* 31 整个开关行都能点（文字也算），手机上点中率高很多 */
.toggle-row{cursor:pointer;border-radius:8px;transition:background-color .15s}
.toggle-row:hover{background:color-mix(in srgb,var(--sunk) 55%,transparent)}
.toggle-row:has(.switch input:disabled){cursor:default;background:none}

/* ==========================================================
   六、表格与列表
   ========================================================== */
/* 32 行上的操作按钮平时压暗，鼠标到了那一行才完全显出来 */
@media (hover:hover) and (min-width:901px){
  .tbl td.act .btn{opacity:.6;transition:opacity var(--t-micro)}
  .tbl tbody tr:hover td.act .btn,.tbl tbody tr:focus-within td.act .btn,.tbl tbody tr.is-active td.act .btn{opacity:1}
}

/* 33 行可以用键盘走：上下键换行、回车打开，选中行左边一条竖线 */
.tbl tbody tr.click{position:relative;outline:none}
.tbl tbody tr.click:focus-visible{background:var(--sunk)}
.tbl tbody tr.click:focus-visible td:first-child::before,.tbl tbody tr.is-active td:first-child::before{
  content:"";position:absolute;left:0;top:6px;bottom:6px;width:3px;border-radius:0 2px 2px 0;background:var(--accent);
}

/* 34 鼠标压住行时轻轻陷下去一点，知道按到了 */
.tbl tbody tr.click:active{background:color-mix(in srgb,var(--sunk) 80%,var(--line))}

/* 35 新进来的行从淡色底慢慢退回正常，眼睛能跟上变化 */
.tbl tr.row-in{animation:rowIn .9s var(--e-out) both}

/* 36 整张表换数据（翻页、筛选）时整体淡入，不是突然替换 */
.tbl-wrap.swap .tbl tbody{animation:tblIn .26s var(--e-out) both}
@keyframes tblIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}

/* 37 排序箭头只在当前排序列显示，鼠标放到别的列才淡淡出现 */
.tbl th[data-sort]{cursor:pointer;user-select:none}
.tbl th[data-sort] .srt{display:inline-block;margin-left:4px;opacity:0;transition:opacity .15s,transform .2s var(--e-out)}
.tbl th[data-sort]:hover .srt{opacity:.45}
.tbl th[aria-sort] .srt{opacity:1}
.tbl th[aria-sort="descending"] .srt{transform:rotate(180deg)}

/* 38 骨架屏的呼吸更慢更浅，不再像在闪 */
.sk{animation:skPulse 1.8s var(--e-out) infinite}
.loading-block .sk:nth-child(2){animation-delay:.08s}
.loading-block .sk:nth-child(3){animation-delay:.16s}
.loading-block .sk:nth-child(n+4){animation-delay:.24s}

/* 39 分页按钮之间的当前页用小圆底，而不是整块变色 */
.pager .btn.sm[aria-current="page"]{background:var(--accent-soft);border-color:transparent;color:var(--accent);font-weight:600}

/* 40 列表翻到底的时候下面补一句"没有更多了"，而不是空着 */
.list-end{padding:14px 0 2px;text-align:center;font-size:12px;color:var(--faint)}

/* ==========================================================
   七、筛选、批量与工具条
   ========================================================== */
/* 41 筛选条件显示成可以单个摘掉的小标签，右边一键清空 */
.filt-bar{display:flex;align-items:center;gap:6px;flex-wrap:wrap;min-height:0;margin-top:10px}
.filt-bar:empty{display:none}
.filt-bar .tag{animation:popIn .2s var(--e-ui)}
.filt-clear{border:0;background:none;color:var(--muted);font-size:12px;padding:3px 6px;border-radius:6px;transition:color .15s,background-color .15s}
.filt-clear:hover{color:var(--ink);background:var(--sunk)}

/* 42 筛选以后在工具条右边安静地写出"筛出多少条 / 共多少条" */
.toolbar .res-n{font-size:12px;color:var(--faint);font-variant-numeric:tabular-nums;transition:opacity .2s}

/* 43 勾选了行之后，底部滑出一条批量操作条（没选中就完全不占地方） */
.bulk-bar{
  position:sticky;z-index:20;bottom:0;display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  margin:10px -4px -4px;padding:10px 14px;border:1px solid var(--line);border-radius:12px;
  background:var(--glass-2);backdrop-filter:blur(14px) saturate(1.4);-webkit-backdrop-filter:blur(14px) saturate(1.4);
  box-shadow:var(--shadow-float);
  transform:translateY(14px);opacity:0;pointer-events:none;
  transition:opacity .2s,transform .26s var(--e-ui);
}
.bulk-bar.show{transform:none;opacity:1;pointer-events:auto}
.bulk-bar b{font-variant-numeric:tabular-nums}

/* 44 搜索时输入框下沿走一条很细的进度线，知道还在找 */
.input-ic.seeking::after{
  content:"";position:absolute;left:1px;right:1px;bottom:1px;height:2px;border-radius:2px;overflow:hidden;
  background:linear-gradient(90deg,transparent,var(--accent),transparent);
  background-size:42% 100%;background-repeat:no-repeat;animation:seek 1s ease-in-out infinite;
}
@keyframes seek{0%{background-position:-45% 0}100%{background-position:145% 0}}

/* ==========================================================
   八、页签与分段控件
   ========================================================== */
/* 45 页签下划线滑过去的时候带一点点"拉长"，更像实体 */
.tabs .ink{transition:transform var(--sp-sheet-ms) var(--sp-sheet),width var(--sp-sheet-ms) var(--sp-sheet)}

/* 46 页签左右能滑时，两端出现渐隐，提示还有别的页签 */
.tabs{position:relative;scroll-behavior:smooth}
.tabs.can-x{
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 18px,#000 calc(100% - 18px),transparent);
  mask-image:linear-gradient(90deg,transparent,#000 18px,#000 calc(100% - 18px),transparent);
}
.tabs.can-x.at-start{-webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 18px),transparent);mask-image:linear-gradient(90deg,#000 calc(100% - 18px),transparent)}
.tabs.can-x.at-end{-webkit-mask-image:linear-gradient(90deg,transparent,#000 18px);mask-image:linear-gradient(90deg,transparent,#000 18px)}
.tabs.can-x.at-start.at-end{-webkit-mask-image:none;mask-image:none}
.tabs.can-x .ink{bottom:0}   /* 蒙版按边框盒裁剪，下划线抬 1px 才不会被切掉 */

/* 47 切页签只做 120ms 的淡入，不做方向位移。切页签是高频动作，位移会让人等；
   方向感由下面滑动的下划线交代，不需要内容再演一遍。 */
.tab-panel.in-l,.tab-panel.in-r{animation:tabIn var(--t-micro) linear both}
@keyframes tabIn{from{opacity:.4}to{opacity:1}}

/* 48 分段控件的白块跟着滑过去，而不是直接跳到下一格 */
.seg{position:relative;isolation:isolate}
.seg .seg-ink{
  position:absolute;z-index:-1;left:0;top:3px;bottom:3px;border-radius:6px;background:var(--surface);
  box-shadow:0 1px 2px rgba(0,0,0,.08);opacity:0;
  transition:transform var(--sp-snappy-ms) var(--sp-snappy),width var(--sp-snappy-ms) var(--sp-snappy),opacity .18s;
}
.seg.has-ink button[aria-pressed="true"]{background:transparent;box-shadow:none}

/* ==========================================================
   九、侧栏与导航
   ========================================================== */
/* 49 当前页的高亮块左边多一道强调色竖线，扫一眼就找得到自己在哪 */
.nav-ink::after{
  content:"";position:absolute;left:0;top:50%;width:3px;height:18px;margin-top:-9px;
  border-radius:0 2px 2px 0;background:var(--accent);
}

/* 50 服务器行鼠标经过时整行底色淡淡亮一下，当前服务器左边有一条线 */
.srv{position:relative}
.srv[aria-current="true"]::before{
  content:"";position:absolute;left:-4px;top:8px;bottom:8px;width:3px;border-radius:2px;background:var(--accent);
}

/* 51 侧栏里的小计数变化时轻轻跳一下，不用盯着看 */
.nav .count.chg,.nav .badge.chg{animation:bump .3s var(--e-ui)}

/* 52 分组折叠用高度动画，不再"啪"地消失 */
.nav-grp-b{overflow:hidden}
.nav-grp-b.anim{transition:height var(--sp-snappy-ms) var(--sp-snappy),opacity .18s}

/* 53 手机侧栏可以往左滑着关掉，不用去点右边的空白 */
.app.nav-drag .side{transition:none!important}
.app.nav-back .side{transition:transform var(--sp-sheet-ms) var(--sp-sheet)!important}
@media (max-width:900px){
  .side{touch-action:pan-y}
  .side::after{content:"";position:absolute;right:5px;top:50%;width:4px;height:44px;margin-top:-22px;border-radius:2px;background:var(--line);opacity:.5}
}

/* 54 手机侧栏打开时主区往后退一点、变暗，层次和弹窗一致 */
@media (max-width:900px){
  .main{transition:transform var(--sp-sheet-ms) var(--sp-sheet),filter .3s;transform-origin:100% 50%}
  .app.nav-open .main{transform:scale(.94) translateX(10px);filter:saturate(.9) brightness(.96);border-radius:14px;overflow:hidden}
}

/* 55 搜索面板里的快捷键说明在手机上没用，直接不显示 */
@media (max-width:640px){.find kbd,.palette-foot{display:none}}

/* ==========================================================
   十、浮层菜单与提示
   ========================================================== */
/* 56 菜单项用上下键走，当前项有底色，回车确定 */
.pop button[data-k]{position:relative}
.pop button.k-on{background:var(--sunk)}

/* 57 菜单里危险项和其它项之间空一行，手滑点错的概率低一些 */
.pop button.danger{margin-top:2px}
.pop button.danger:hover{background:var(--high-bg)}

/* 58 【已撤销】菜单逐项出现。开菜单是一天上百次的例行动作，逐项入场只会让它变慢；
   整块浮层本来就有 140ms 的缩放淡入，够了。这里只保证菜单项自己的 hover 够快。 */
.pop button{transition:background-color var(--t-micro),color var(--t-micro)}

/* 59 鼠标停住才出提示，移开立刻收；提示自己带一点点位移 */
.tipbox{
  position:fixed;z-index:130;max-width:280px;padding:6px 9px;border-radius:7px;
  background:var(--ink);color:var(--surface);font-size:12px;line-height:1.55;
  box-shadow:var(--shadow-float);pointer-events:none;
  opacity:0;transform:translateY(3px);transition:opacity .14s,transform .18s var(--e-out);
}
.tipbox.show{opacity:1;transform:none}
/* 同一组里连着看第二个提示时不再做动画，直接到位 */
.tipbox.instant{transition-duration:0ms}

/* 60 可以复制的内容鼠标放上去显出图标，点完变成勾 */
.copy.ok svg{opacity:1;color:var(--ok);animation:iconSwap .3s var(--e-ui)}

/* ==========================================================
   十一、数值与状态
   ========================================================== */
/* 61 所有数字统一等宽，刷新时左右不晃 */
.stat .v,.tbl .num,.kv dd,.tag,.count,.badge,.srv-meta,.pager{font-variant-numeric:tabular-nums}

/* 62 数字变化时朝变化方向轻轻滑一下（涨往上、跌往下） */
.n-up{animation:nUp .4s var(--e-out)}
.n-down{animation:nDown .4s var(--e-out)}
@keyframes nUp{from{transform:translateY(5px);opacity:.2}to{transform:none;opacity:1}}
@keyframes nDown{from{transform:translateY(-5px);opacity:.2}to{transform:none;opacity:1}}

/* 63 在线圆点不再呼吸：一屏上百个点同时喘气是最典型的"AI 味"，而且它一直在动却从不传递新信息。
   改成实心点 + 外圈一道静止的浅色描边表示"在线"，状态真的变化时才动一下（见 64）。
   想要回来：把下面这段删掉即可。 */
.dot.live{animation:none;box-shadow:0 0 0 2.5px color-mix(in srgb,var(--ok) 22%,transparent)}
.dot.live.chg{animation:dotIn .3s var(--e-ui)}
@keyframes dotIn{from{box-shadow:0 0 0 0 color-mix(in srgb,var(--ok) 55%,transparent)}to{box-shadow:0 0 0 2.5px color-mix(in srgb,var(--ok) 22%,transparent)}}

/* 64 细线进度条到位时末端有一个小圆头，看起来是"走到这里"而不是"切断" */
.meter > i{position:relative}
.meter > i::after{
  content:"";position:absolute;right:0;top:50%;width:6px;height:6px;margin-top:-3px;border-radius:50%;
  background:inherit;box-shadow:0 0 0 2px var(--surface);
}
.meter > i[style*="width:0"]::after,.meter > i[style*="width: 0"]::after{display:none}

/* 65 徽标数字从 0 变成有值时从中间长出来 */
.badge.in{animation:popIn .24s var(--e-ui)}

/* ==========================================================
   十二、手机端
   ========================================================== */
@media (max-width:900px){
  /* 66 所有能点的东西至少 44px 高，不用瞄 */
  .btn:not(.sm):not(.icon){min-height:42px}
  .tbl td.act .btn.icon{width:38px;height:38px}
  .pop button{height:42px;font-size:14px}
  .palette-item{min-height:48px}

  /* 67 浮层菜单在手机上改成从底部弹起的列表，拇指够得到 */
  .pop.sheet{
    left:8px!important;right:8px!important;top:auto!important;bottom:calc(8px + env(safe-area-inset-bottom,0px))!important;
    width:auto;min-width:0;max-height:70dvh;overflow:auto;padding:6px;border-radius:16px;
    transform:translateY(14px);transform-origin:50% 100%!important;
  }
  .pop.sheet.show{transform:none}
  .pop.sheet button{height:46px;border-radius:10px}

  /* 68 下拉刷新：往下拉一点出现一个小圈，松手就刷新 */
  .ptr{
    position:absolute;z-index:30;left:50%;top:0;width:28px;height:28px;margin-left:-14px;
    display:grid;place-items:center;border-radius:50%;background:var(--surface);border:1px solid var(--line);
    color:var(--muted);box-shadow:var(--shadow-float);opacity:0;pointer-events:none;
  }
  .ptr svg{width:15px;height:15px;transition:transform .2s}
  .ptr.run svg{animation:spin .8s linear infinite}

  /* 69 键盘弹出来时底部按钮跟着上去，不被挡住 */
  .modal-foot,.drawer-foot{padding-bottom:calc(12px + env(safe-area-inset-bottom,0px))}

  /* 70 横屏时左右也让开刘海 */
  .page-main,.rail,.topbar{padding-left:max(16px,env(safe-area-inset-left,0px));padding-right:max(16px,env(safe-area-inset-right,0px))}
}

/* 71 窄屏时把表格换成一行行的卡片，不用左右拖（需要表格带 data-card 才生效） */
@media (max-width:640px){
  .tbl[data-card]{display:block}
  .tbl[data-card] thead{display:none}
  .tbl[data-card] tbody,.tbl[data-card] tr{display:block}
  .tbl[data-card] tr{padding:12px 2px;border-bottom:1px solid var(--line)}
  .tbl[data-card] td{display:flex;align-items:center;gap:10px;height:auto;min-height:30px;padding:3px 0;border:0}
  .tbl[data-card] td::before{content:attr(data-l);flex:none;width:76px;color:var(--muted);font-size:12px}
  .tbl[data-card] td.act{justify-content:flex-end;padding-top:8px}
  .tbl[data-card] td.act::before{display:none}
}

/* ==========================================================
   十三、安静一点的视觉
   ========================================================== */
/* 72 次要说明再降一档对比度，正文更突出 */
.field .hint,.toggle-row small{color:color-mix(in srgb,var(--faint) 88%,transparent)}

/* 73 卡片之间用间距分组，去掉连着排时的双线 */
.card + .card{margin-top:14px}
.stack > .card + .card{margin-top:0}

/* 74 分隔线再浅一点，密集页面不会像格子纸 */
.sep{background:color-mix(in srgb,var(--line) 72%,transparent)}

/* 75 鼠标移到卡片上时边框轻轻变深，不加阴影也知道这块是可交互的 */
.card.act-card{transition:border-color .2s,transform var(--sp-default-ms) var(--sp-default)}
.card.act-card:hover{border-color:var(--line-2)}

/* 76 空状态里的下一步按钮直接放出来，不用再去别处找 */
.empty .btn{min-height:40px}
.empty .empty-alt{display:block;margin-top:10px;font-size:12.5px;color:var(--faint)}

/* 77 标题行和操作按钮在窄屏自动换行后左对齐，不再挤成一坨 */
@media (max-width:900px){
  .card-head{flex-wrap:wrap;row-gap:8px}
  .card-head .right{margin-left:0;width:100%}
}

/* 78 焦点框统一成强调色细环，键盘用户不会迷路 */
a:focus-visible,button:focus-visible,[tabindex]:focus-visible,summary:focus-visible,.switch input:focus-visible + i{
  outline:2px solid var(--accent);outline-offset:2px;
}
.modal-body :focus-visible{scroll-margin:16px}

/* ==========================================================
   十四、快捷键面板
   ========================================================== */
/* 79 按 ? 弹出全部快捷键，不用去翻文档 */
.keys{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:4px 28px}
.keys dl{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 14px;align-items:center;margin:0}
.keys dt{color:var(--ink-2);font-size:13px}
.keys dd{margin:0;display:flex;gap:4px}
.keys kbd{
  min-width:22px;padding:2px 6px;border:1px solid var(--line);border-bottom-width:2px;border-radius:5px;
  background:var(--sunk);color:var(--muted);font:inherit;font-size:11.5px;line-height:16px;text-align:center;
}
.keys h4{margin:14px 0 6px;font-size:12px;color:var(--muted);font-weight:600}

/* ==========================================================
   十五、按下的手感（统一到 0.96 / 150ms / ease-out）
   —— 这是"按下去有反应"这件事唯一的通用做法，之前各处 .93 / .98 不一
   ========================================================== */
/* 80 所有按钮按下时缩到 0.96，禁用的不缩 */
.btn:not(:disabled):not([aria-disabled="true"]):active,
.chip:not(:disabled):active,.seg button:not(:disabled):active,
.lm-zoom button:active,.pop button:active{
  transform:scale(.96);transition-duration:var(--t-press);transition-timing-function:ease-out;
}
.ai-fab:active{transform:scale(.96)}
/* 81 行、卡片这种大面积的按下不缩放，只压底色，缩放在大块上看着像塌了 */
.tbl tbody tr.click:active,.card.act-card:active{transform:none}

/* ==========================================================
   十六、实时对局（163–205）
   这一页和别处不一样：它一直在动。所以这里的原则反过来——
   能不加动效就不加，省下来的注意力留给画面本身真正的变化。
   ========================================================== */

/* 163 页面高度改用 dvh：手机地址栏收起 / 展开时，底部的播放条不会被切掉。
   只改宽屏那一档 —— 窄屏本来就是 height:auto 的整页滚动，别把它又锁回一屏。 */
@media (min-width:1081px){
  .lm-wrap{height:calc(100dvh - var(--top-h,64px) - 32px)}
}

/* 195 顶栏在窄屏下换行后，右边那排按钮不要挤成一团 */
@media (max-width:900px){
  .lm-top-actions{flex-wrap:wrap;row-gap:6px}
  .lm-top-actions .btn{min-height:36px}
}

/* 164 回放状态要一眼能认出来：顶部压一条琥珀色的边，而不是只把比分调淡 1% */
.lm-wrap.is-replay .lm-top{border-color:color-mix(in srgb,var(--mid) 55%,var(--line));box-shadow:inset 0 2px 0 var(--mid)}
.lm-wrap.is-replay .lm-tk{opacity:1}
.lm-wrap.is-replay .lm-stage{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--mid) 30%,transparent)}
/* 165 回放时在地图左下角常驻一个"回放中"的角标，不抢位置也跑不掉 */
.lm-wrap.is-replay .lm-stage::after{
  content:"回放中";position:absolute;z-index:6;left:10px;bottom:8px;
  padding:2px 8px;border-radius:6px;font-size:11px;letter-spacing:.02em;
  background:var(--lm-ov);color:#ffd76a;border:1px solid var(--lm-ov-line);pointer-events:none;
}

/* 166 实时 / 回放按钮保留文字和图标两重提示，不靠颜色单独表达 */
.lm-ctrl [data-live].on::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;margin-right:5px;flex:none}
.lm-ctrl [data-live]{display:inline-flex;align-items:center}

/* 167 图层面板从"直接出现"改成 160ms 的落下；用 visibility 不用 display，才能做过渡 */
.lm-layers{display:flex!important;visibility:hidden;opacity:0;pointer-events:none;
  transform:translateY(-6px) scale(.985);transform-origin:12px 0;
  transition:opacity .16s,transform .18s var(--e-ui),visibility 0s .18s;}
.lm-stage.lm-show-layers .lm-layers{visibility:visible;opacity:1;pointer-events:auto;transform:none;transition:opacity .16s,transform .18s var(--e-ui),visibility 0s}
@media (max-width:640px){.lm-layers{transform-origin:12px 0}}

/* 168 图层开关一行一行太密，鼠标很容易点错；给每项一个可点的整块底 */
.lm-layers .lm-lp-i label{padding:3px 6px;border-radius:6px;transition:background-color var(--t-micro)}
.lm-layers .lm-lp-i label:hover{background:var(--sunk)}
.lm-layers .lm-lp-i input{width:13px;height:13px}

/* 169 "全选 / 全不选"这种文字按钮太小，给它一个点得到的热区 */
.lm-lp-all{padding:3px 7px;border-radius:6px;transition:background-color var(--t-micro)}
.lm-lp-all:hover{background:var(--accent-soft)}

/* 170 折叠左右栏的按钮原来只有 14px 的字，手机上基本点不中 */
.lm-unfold{gap:4px}
.lm-fold{width:30px;height:30px;display:grid;place-items:center;border-radius:8px;
  background:var(--lm-ov);color:var(--lm-ov-ink);border:1px solid var(--lm-ov-line);transition:background-color var(--t-micro)}
.lm-fold:hover{background:color-mix(in srgb,var(--lm-ov) 80%,#fff)}
@media (max-width:900px){.lm-fold{width:38px;height:38px}}

/* 171 地图上的横幅从顶部落下来（一局里只会出现几次，值得一个 200ms 的交代） */
.lm-banner{transition:opacity .18s,transform .2s var(--e-ui)}
.lm-banner:not(.show){opacity:0;transform:translateX(-50%) translateY(-8px)}
.lm-banner.show{display:block;opacity:1;transform:translateX(-50%)}

/* 172 拖图片进来时的提示框淡入，不是"啪"地出现 */
.lm-drop{display:flex!important;opacity:0;pointer-events:none;transition:opacity .14s}
.lm-drop.show{opacity:1}

/* 173 右键玩家的浮窗从点击的位置长出来，而不是从自己的中心 */
.lm-pop{transform-origin:var(--po-x,0) var(--po-y,0);animation:lmPop .18s var(--e-ui)}
@keyframes lmPop{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:none}}

/* 174 时间轴上悬停时跟着鼠标走一个时间标签，取代慢半拍的系统 title */
.lm-scrub{
  position:fixed;z-index:70;transform:translate(-50%,-100%);pointer-events:none;
  padding:3px 7px;border-radius:6px;font-size:11.5px;font-variant-numeric:tabular-nums;
  background:var(--lm-ov);color:var(--lm-ov-ink);border:1px solid var(--lm-ov-line);
  opacity:0;transition:opacity .1s;
}
.lm-scrub.show{opacity:1}

/* 175 时间轴本身加高一点、给出明确的"可以拖"的指针 */
.lm-tl{cursor:ew-resize}
@media (max-width:900px){.lm-tl{height:52px}}

/* 176 手机上底部播放条吸底并让开安全区，翻侧栏时它不会跟着跑掉 */
@media (max-width:1080px){
  .lm-bottom{position:sticky;bottom:0;z-index:7;
    padding-bottom:calc(8px + env(safe-area-inset-bottom,0px));
    background:var(--glass-2);backdrop-filter:blur(14px) saturate(1.4);-webkit-backdrop-filter:blur(14px) saturate(1.4)}
}

/* 196 手机上 AI 悬浮球正好压在时间轴上，拖进度条必然误触。
   播放条吸底之后这块地方是固定被占住的，往上挪会轮流压到缩放按钮和比例尺，
   所以这一页在窄屏直接把球收起来 —— 这一页自带「AI 建议」「AI 复查」两个入口，不缺这一个。
   想保留的话：把 display:none 换成 left:16px;right:auto;bottom:calc(126px + env(safe-area-inset-bottom,0px))，
   它会落到地图左下角（会压住比例尺）。 */
@media (max-width:1080px){
  body.pg-live .ai-fab{display:none}
}

/* 197 手机上顶部比分区占了 204px（四分之一屏）。副文案收到两行，整块压到 150px 以内 */
@media (max-width:640px){
  .lm-top{padding:8px 12px}
  .lm-sub{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
  .lm-score .lm-mid{max-width:34%}
  .lm-tk{font-size:22px}
}

/* 177 左右两栏的分组标题吸顶时补一道细影，知道上面还有内容 */
.lm-grp{box-shadow:0 1px 0 var(--line),0 6px 10px -12px var(--edge)}
.lm-evbar{box-shadow:0 1px 0 var(--line)}

/* 178 列表里选中的那一行底色过渡 120ms，连续按上下键时不会闪 */
.lm-row,.lm-ev,.lm-anc{transition:background-color var(--t-micro)}
.lm-row.sel{box-shadow:inset 3px 0 0 var(--accent)}

/* 179 血条、部位血量的变化给 200ms，数字和条同步，不会一个跳一个不动 */
.lm-hp b,.lm-pbar i,.sa-fb i,.sa-flagbar i{transition:width .2s linear}

/* 180 态势天平的指针原来用 left 做 600ms 的动画（会触发重排，而且太慢）
   先把时长压到 320ms 并换成生产曲线；彻底改成 transform 需要动 situation.js 一行，见清单 */
.sa-scale b{transition:left .32s var(--e-ui)}

/* 181 右侧"态势"里的卡片之间用间距分组，去掉一层层的边框套边框 */
.sa{gap:10px}
.sa-card{border-color:color-mix(in srgb,var(--line) 80%,transparent)}

/* 182 事件列表里新来的重要事件闪一次底色就停，不做持续动画 */
.lm-ev.ev-new{animation:evIn 1.1s var(--e-out) both}
@keyframes evIn{from{background:color-mix(in srgb,var(--accent) 16%,transparent)}to{background:transparent}}

/* 183 长列表继续用 content-visibility 跳过屏外渲染（2.6.5 已有），这里补上事件列表 */
.lm-virt>.lm-ev{content-visibility:auto;contain-intrinsic-size:auto 30px}

/* 184 地图上的浮层统一圆角和阴影，之前 8 / 10 / 12 / 16px 混用 */
.lm-tip,.lm-coord,.lm-banner{border-radius:8px}
.lm-zoom,.lm-layers{border-radius:10px}

/* 185 缩放按钮的分隔线用描边不用边框，强制高对比模式下也还看得见 */
.lm-zoom button+button{box-shadow:inset 0 1px 0 var(--lm-ov-line)}

/* 186 地图画布拖动时关掉文字选中，手机上长按不会误选到旁边的列表 */
.lm-stage{user-select:none;-webkit-user-select:none}
.lm-stage canvas.drag{will-change:transform}

/* 187 两侧栏里的滚动不传给页面 */
.lm-list,.lm-plist,.lm-detail,.lm-mappanel,.lm-pop-b,.lm-anchors{overscroll-behavior:contain}

/* 188 详情区往下滚时顶部出现细线（和对话框一套做法） */
.lm-detail{border-top-color:var(--line);transition:box-shadow .2s}
.lm-detail.is-scroll{box-shadow:inset 0 6px 8px -10px var(--edge)}

/* 189 玩家栏的搜索框有字时也给清除按钮（走通用那套，这里只补间距） */
.lm-pc-q.input-ic.has-val .input{padding-right:30px}
/* 没有 .input-ic 包着的输入框（实时对局里的几个），按钮由脚本按位置摆 */
.in-clear-host{position:relative}
.in-clear-host > .in-clear{position:absolute;width:22px;height:22px;display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--faint);transition:opacity .15s,transform .18s var(--e-ui),color .15s}
.in-clear-host > .in-clear:hover{color:var(--ink);background:var(--sunk)}

/* 190 手机上地图占屏高从 62vh 改成 dvh，并给一个最小值，键盘弹出时不塌 */
@media (max-width:1080px){.lm-body{grid-template-rows:minmax(320px,58dvh) auto}}

/* 192 修一个布局 bug：玩家栏标题和阵营分段挤在一行，"玩家"两个字被压成竖排。
   让这一行可以换行，标题和人数不参与压缩 */
.lm-pc-head{flex-wrap:wrap;row-gap:6px}
.lm-pc-head > b,.lm-pc-head > .tn{flex:none;white-space:nowrap}
.lm-pc-head > .grow{flex:1 1 0;min-width:0}
.lm-pc-head .seg{flex:1 1 100%;justify-content:stretch}
.lm-pc-head .seg button{flex:1 1 0;max-width:none}

/* 193 "收起玩家栏 / 收起态势栏"的箭头按钮也给够点击区 */
.lm-pc-head .x-btn{width:26px;height:26px;display:grid;place-items:center;border-radius:6px}
@media (max-width:900px){.lm-pc-head .x-btn{width:36px;height:36px}}

/* 194 比分数字变化时朝方向轻滑一下（一局里只变几十次，值得） */
.lm-tk.n-up{animation:nUp .34s var(--e-ui)}
.lm-tk.n-down{animation:nDown .34s var(--e-ui)}

/* ==========================================================
   十七、这一页上"刻意没有加"的动效（留档，免得下轮又加回来）
   · 地图悬停提示（.lm-tip）：鼠标在地图上一直在动，提示框每秒出现几十次，
     任何入场动画都会变成闪烁。保持瞬显瞬隐。
   · 左右栏页签（.lm-tabs）：切页签是高频动作，不加滑动下划线。
   · 在线 / 实时的圆点：不做呼吸、不做光晕。
   · 画布内的单位：不做 hover 放大，地图上几百个点，放大等于全屏抖动。
   · 事件列表：不做逐条进入。事件是一条条推进来的，stagger 会让最新的一条最后才到。
   ========================================================== */

/* ==========================================================
   十八、降级：系统"减弱动态效果"或站点动效关掉时
   —— 不是粗暴地 animation:none（那样元素会"啪"地出现），而是换成纯不透明度的交叉淡入
   ========================================================== */
@media (prefers-reduced-motion:reduce){
  /* 位移、缩放、模糊全部取消，只留 120ms 的淡入 */
  .to-top,.rt-bar,.toast .t-bar,.tbl tr.row-in,.tbl-wrap.swap .tbl tbody,
  .tab-panel.in-l,.tab-panel.in-r,.badge.in,.btn.did svg,.copy.ok svg,
  .filt-bar .tag,.lm-pop,.lm-ev.ev-new,.lm-tk.n-up,.lm-tk.n-down,.dot.live.chg{
    animation:fadeOnly .12s linear both!important;
  }
  @keyframes fadeOnly{from{opacity:0}to{opacity:1}}
  .to-top{transition:opacity .12s}
  .to-top.show{transform:none}
  .side-scroll.can-scroll,.palette-list.can-scroll,.tabs.can-x{-webkit-mask-image:none!important;mask-image:none!important}
  .toasts .toast{transform:none!important}
  .lm-banner:not(.show),.lm-layers{transform:none!important}
  .sa-scale b,.lm-hp b,.lm-pbar i,.sa-fb i,.sa-flagbar i{transition:none}
  .btn:active,.chip:active,.seg button:active,.ai-fab:active,.lm-zoom button:active,.pop button:active{transform:none}
  @media (max-width:900px){.app.nav-open .main{transform:none;border-radius:0}}
}
html[data-motion="off"] .to-top,html[data-motion="off"] .rt-bar,html[data-motion="off"] .toast .t-bar,
html[data-motion="off"] .lm-pop,html[data-motion="off"] .lm-ev.ev-new,html[data-motion="off"] .dot.live.chg{animation:none!important}
html[data-motion="off"] .toasts .toast,html[data-motion="reduced"] .toasts .toast{transform:none!important}
html[data-motion="off"] .app.nav-open .main{transform:none;border-radius:0}
html[data-motion="off"] .lm-layers{transition:none}
@media (prefers-reduced-transparency:reduce){
  .to-top,.bulk-bar,.lm-bottom{backdrop-filter:none!important;-webkit-backdrop-filter:none!important;background:var(--surface)!important}
}
@media (forced-colors:active){
  /* 高对比模式会抹掉所有 box-shadow，靠阴影表达的边界要退回描边 */
  .to-top,.bulk-bar,.lm-fold,.lm-zoom{border:1px solid currentColor}
  .tbl tbody tr.click:focus-visible td:first-child::before,.lm-row.sel{outline:2px solid currentColor}
}

/* ==========================================================
   十九、第五轮：精致度（241–262）
   上一轮撤掉了逐项入场、呼吸圆点、工具动作的过冲、scale(.4) 起步，这一轮不加回来。
   精致感从别处补：
   · 手感统一 —— 上一轮只把 5 处工具动作的过冲换掉了，菜单、提示条、按钮松手、开关、
     导航高亮块、对话框都还在用带过冲的弹簧。这一轮全部收齐，一个面板只剩一种手感
   · 分层时序 —— 透明度先到位、位移后到位；进来慢、出去快（退场约为入场的一半）
   · 动得有交代 —— 只在真的发生变化的那一刻动：哪几位数字变了就滚哪几位，哪个人上线就扩一圈
   · 材质 —— 浮层用两层阴影（环境光 + 主光）和一道顶部内高光，代替一团均匀的灰影
   频率闸门照旧：一天上百次的动作 ≤ 180ms 且不加戏；一个月几次的（换主题）才给 500ms 的展开
   ========================================================== */
:root{
  --e-enter:cubic-bezier(.16,1,.3,1);   /* 进场：快起、长尾减速，无过冲 */
  --e-exit:cubic-bezier(.4,0,1,1);      /* 退场：加速离开 */
  --e-move:cubic-bezier(.65,0,.35,1);   /* 状态之间移动 */
  --hl:rgba(255,255,255,.62);           /* 浮层顶部内高光 */
  --sh-amb:rgba(38,33,22,.07);          /* 环境光阴影：贴身、很浅 */
  --sh-key:rgba(38,33,22,.17);          /* 主光阴影：落在下方、比较远 */
  --elev:0 1px 2px var(--sh-amb),0 4px 10px -2px var(--sh-amb),0 18px 36px -14px var(--sh-key),inset 0 1px 0 var(--hl);
}
:root[data-theme="dark"]{--hl:rgba(255,255,255,.055);--sh-amb:rgba(0,0,0,.32);--sh-key:rgba(0,0,0,.55)}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]):not([data-theme="white"]){--hl:rgba(255,255,255,.055);--sh-amb:rgba(0,0,0,.32);--sh-key:rgba(0,0,0,.55)}}

/* 241 浮层菜单：去掉过冲。原来 scale(.94) → 1 用 520ms 带过冲的弹簧，一天开上百次，每次都晃一下。
   现在从触发它的那一侧落下 4px + 缩放 .97，透明度 120ms 先到位、位移 180ms 后到位；关掉只淡出 90ms */
.pop{opacity:0;transform:translateY(-4px) scale(.97);transition:opacity 90ms linear,transform 90ms var(--e-exit)}
.pop[style*="px 100%"]{transform:translateY(4px) scale(.97)}           /* 往上开的菜单从下往上落 */
@media (max-width:900px){.pop.sheet,.pop.sheet[style*="px 100%"]{transform:translateY(14px)}.pop.sheet.show{transform:none}}
.pop.show,.pop[style*="px 100%"].show{opacity:1;transform:none;transition:opacity 120ms linear,transform 180ms var(--e-enter)}
/* 242 修 bug：菜单项按下的缩放没有过渡（transition 里漏了 transform），按下是直接跳到 0.96 的 */
.pop button{transition:background-color var(--t-micro),color var(--t-micro),transform var(--t-press) ease-out}
.lm-zoom button{transition:background-color var(--t-micro),transform var(--t-press) ease-out}

/* 243 按钮、标签、分段：按下 150ms 到 0.96（上一轮已统一），松手从 350ms 带过冲改成 220ms 无过冲 */
.btn{transition:transform 220ms var(--e-ui),background-color .15s,border-color .15s,color .15s,opacity .15s,box-shadow .15s}
.chip,.seg button,.srv,.team-pill,.qr-btn,.role-card,.swatch{transition:transform 220ms var(--e-ui),background-color .15s,border-color .15s,color .15s,box-shadow .2s}

/* 244 开关：滑块去掉过冲（240ms），轨道颜色等滑块走过一半才换，颜色和位置不再各走各的 */
.switch i::after{transition:transform 240ms var(--e-ui),width 160ms var(--e-ui)}
.switch i{transition:background-color 110ms linear 90ms}

/* 245 导航高亮块、页签下划线：去掉过冲。页签和分段控件的滑块改成「前沿先走、后沿跟上」的拉伸，
   由 v2916.js 用 transform 驱动（不动 width，不触发重排） */
.nav-wrap.ready .nav-ink{transition:transform 300ms var(--e-ui),height 300ms var(--e-ui),opacity .2s}
.tabs .ink.fx,.seg .seg-ink.fx{transition:opacity .18s!important;transform-origin:0 50%}

/* 246 提示条：入场去掉过冲（300ms 无过冲上浮 10px），退场 160ms 加速沉下去；
   一条关掉、一条新来时，其余几条平滑补位，不再「咔」地跳一格（脚本用独立的 translate 属性做，不和堆叠缩放打架） */
.toast{transform:translateY(10px) scale(.98);transition:opacity 140ms linear,transform 160ms var(--e-exit)}
.toast.show{transform:none;transition:opacity 160ms linear,transform 300ms var(--e-enter)}
.toast{box-shadow:0 1px 2px var(--sh-amb),0 10px 24px -10px var(--sh-key),inset 0 1px 0 rgba(255,255,255,.08)}

/* 247 浮层材质：两层阴影（贴身的环境光 + 落在下方的主光）+ 一道顶部内高光，
   代替原来一团均匀的灰影。浅色主题下像纸放在桌上，深色主题下边缘不再糊进背景 */
.pop,.palette,.tipbox{box-shadow:var(--elev)}
.modal{box-shadow:var(--shadow-pop),inset 0 1px 0 var(--hl)}
.drawer{box-shadow:-1px 0 0 var(--line),-18px 0 40px -18px var(--sh-key)}

/* 248 悬停提示：加一个指向目标的小三角；从三角所在的那一点展开；翻到目标下方时，三角和位移方向一起翻转 */
.tipbox{transform-origin:var(--tip-x,50%) 100%;transform:translateY(3px) scale(.97)}
.tipbox.below{transform-origin:var(--tip-x,50%) 0;transform:translateY(-3px) scale(.97)}
.tipbox.show{transform:none}
.tipbox::after{content:"";position:absolute;left:var(--tip-x,50%);top:100%;width:8px;height:4px;margin-left:-4px;
  background:inherit;clip-path:polygon(0 0,100% 0,50% 100%)}
.tipbox.below::after{top:auto;bottom:100%;clip-path:polygon(50% 0,100% 100%,0 100%)}

/* 249 数字逐位滚动：只滚变了的那几位（187 → 188 只动最后一位），上下各带一道淡出，
   不再整个数字一起往上滑。用在侧栏计数、实时对局比分、提示条合并次数 */
.od{display:inline-block;position:relative;overflow:hidden;vertical-align:top;
  -webkit-mask-image:linear-gradient(transparent,#000 22%,#000 78%,transparent);mask-image:linear-gradient(transparent,#000 22%,#000 78%,transparent)}
.od > .od-n{display:inline-block;animation:odIn .42s var(--e-enter) both}
.od > .od-o{position:absolute;left:0;top:0;animation:odOut .42s var(--e-enter) both}
@keyframes odIn{from{transform:translateY(calc(var(--od,1) * 100%));opacity:0}to{transform:none;opacity:1}}
@keyframes odOut{from{transform:none;opacity:1}to{transform:translateY(calc(var(--od,1) * -100%));opacity:0}}

/* 250 在线状态：仍然不呼吸。只在「刚上线」那一刻向外扩一圈就停（700ms，一次）；
   下线时圆点 300ms 褪成灰，不是啪地变色。上一轮写了 .dot.live.chg 的样式，但没有任何脚本加这个类，这次由脚本接上 */
.dot{transition:background-color .3s}
.dot.ping{animation:dotPing .7s var(--e-enter) both}
@keyframes dotPing{from{box-shadow:0 0 0 0 color-mix(in srgb,var(--ok) 65%,transparent)}to{box-shadow:0 0 0 9px color-mix(in srgb,var(--ok) 0%,transparent)}}

/* 251 完成的勾：图标先交叉淡入，接着勾的笔画在 260ms 里描出来，而不是一个现成的勾缩放出现 */
.copy.ok svg,.btn.did svg{opacity:1}
.ck-draw path,.ck-draw polyline{stroke-dasharray:1;stroke-dashoffset:1;animation:ckDraw .26s var(--e-enter) .08s forwards}
@keyframes ckDraw{to{stroke-dashoffset:0}}

/* 252 列表项移除（处置台处理、各处的收起）：先淡出、再收拢。
   原来高度和透明度一起收，卡片里的字会被从下往上挤扁；现在 140ms 内先淡掉，空位再用 260ms 合上。
   撤销回来的那一张闪一次底色，知道它回到了哪里 */
.inc.inc-back{animation:incBack 1.2s var(--e-out) both}
@keyframes incBack{from{background:color-mix(in srgb,var(--accent) 12%,var(--surface));border-color:color-mix(in srgb,var(--accent) 40%,var(--line))}to{background:var(--surface)}}

/* 253 骨架屏：整块明暗呼吸换成一道低对比的斜向光泽扫过（只在加载时有，数据到了就没了） */
.sk{position:relative;overflow:hidden;animation:none}
.sk::after{content:"";position:absolute;inset:0;transform:translateX(-100%);pointer-events:none;
  background:linear-gradient(100deg,transparent 25%,color-mix(in srgb,var(--surface) 62%,transparent) 50%,transparent 75%);
  animation:skSheen 1.7s var(--e-move) infinite}
.loading-block .sk:nth-child(2)::after{animation-delay:.08s}
.loading-block .sk:nth-child(3)::after{animation-delay:.16s}
.loading-block .sk:nth-child(n+4)::after{animation-delay:.24s}
@keyframes skSheen{to{transform:translateX(100%)}}

/* 254 抽屉：面板滑进来的同时，里面的内容整体多走 18px 再落定 —— 同时到位，只是有了一点纵深。
   不是逐段入场（那条上一轮撤掉了，不加回来） */
.drawer .drawer-body{transition:transform 560ms var(--e-enter)}
.drawer:not(.show) .drawer-body{transform:translateX(18px)}
.drawer.is-drag .drawer-body{transition:none}
@media (max-width:640px){.drawer:not(.show) .drawer-body{transform:translateY(22px)}}

/* 255 回到顶部：外圈加一道跟着滚动走的进度环，一眼看出翻到哪了（和滚动 1:1，没有自己的动画） */
.to-top .tt-ring{position:absolute;inset:-1px;width:calc(100% + 2px);height:calc(100% + 2px);transform:rotate(-90deg);pointer-events:none}
.to-top .tt-ring circle{fill:none;stroke:var(--accent);stroke-width:1.75;stroke-linecap:round;stroke-dasharray:100;stroke-dashoffset:100;opacity:.85}

/* 256 换主题：一个月才换几次，值得一个交代 —— 新主题从你点的那个位置圆形展开（520ms），
   不再整页一起淡入淡出。由 v2916.js 接管 startViewTransition */
html.vt-reveal::view-transition-old(root),html.vt-reveal::view-transition-new(root){animation:none;mix-blend-mode:normal}
html.vt-reveal::view-transition-new(root){z-index:2}

/* 257 实时对局（修布局）：宽屏没有顶栏，却按 64px 的顶栏扣了高度，播放条下面空着 96px */
@media (min-width:1081px){.lm-wrap{height:calc(100dvh - var(--top-h,0px) - 12px)}}
/* 258 实时对局（修布局）：右栏「压家 / 郊游」这一节的标题没有左右内边距，贴着边框，和下面的文字不对齐 */
.lm-list > .sa-sec > .sa-h{padding:10px 12px 0}

/* 259 底部面板 / 抽屉拖拽：往回拉的阻尼改成越拉越重的橡皮筋，松手回弹带着手上的速度（见 v2916.js）
   260 对话框：从按钮长出来的起始缩放下限 .16 → .5，弹簧换成 380ms 无过冲（见 v2916.js） */

/* 261 剩下几处还在过冲的也收掉：抽屉、手机侧栏、页面后退、搜索面板。
   进场 420ms 长尾减速，退场 260ms 加速离开 —— 出去比进来快，像把东西放回原处 */
.drawer{transition:transform 260ms var(--e-exit)}
.drawer.show{transition:transform 420ms var(--e-enter)}
@media (max-width:640px){.drawer,.drawer.wide{transition:transform 260ms var(--e-exit)}.drawer.show{transition:transform 420ms var(--e-enter)}}
.app{transition:transform 420ms var(--e-enter),border-radius .35s var(--ease),filter .35s}
.app.nav-back .side{transition:transform 360ms var(--e-enter)!important}
@media (max-width:900px){.main{transition:transform 420ms var(--e-enter),filter .3s}}
.palette{transition:opacity .2s,transform 320ms var(--e-enter)!important}
.tabs .ink{transition:transform 300ms var(--e-ui),width 300ms var(--e-ui)}
.modal.sheet-back{transition:transform 360ms var(--e-enter)!important}

/* 262 实时对局：257 把播放条放到底以后，宽屏的 AI 悬浮球会压住右端的「本局 26:39」，给它让出位置 */
@media (min-width:1081px){body.pg-live:has(.ai-fab) .lm-bottom{padding-right:84px}}

/* ---------- 第五轮的降级 ---------- */
@media (prefers-reduced-motion:reduce){
  .od > .od-n,.od > .od-o,.dot.ping,.ck-draw path,.ck-draw polyline,.inc.inc-back{animation:none!important}
  .ck-draw path,.ck-draw polyline{stroke-dashoffset:0}
  .sk::after{animation:none;display:none}
  .sk{animation:skPulse 1.8s ease-in-out infinite}
  .drawer:not(.show) .drawer-body{transform:none}
  .pop,.pop[style*="px 100%"],.tipbox,.tipbox.below,.toast{transform:none!important}
}
html[data-motion="off"] .od > .od-n,html[data-motion="off"] .od > .od-o,html[data-motion="off"] .dot.ping,
html[data-motion="off"] .sk::after{animation:none!important}
html[data-motion="off"] .drawer:not(.show) .drawer-body{transform:none}
@media (forced-colors:active){.tipbox::after{background:CanvasText}}
