/* ============================================================================
   magpie-motion.css · magpie 的动效层（可复用片段）
   ----------------------------------------------------------------------------
   来源：yetone/magpie @ dcbae2ef · internal/gui/assets/app.css、routing.css、
        library.css、site/public/index.html（内联样式）
   提取日期：2026-10-29 · 数值逐个抄自上游，未做「凭感觉改一下」的调整
   依赖：../tokens/magpie-ui-tokens.css（--mp-* 变量与缓动）

   八条动效，对应 magpie 界面里八处真实交互。每条都能单独拿走用。
   共同点：都在 120–570ms，都用非线性的实心缓动，都尊重 reduced-motion。
   ========================================================================== */

/* ---------------------------------------------------------------------------
   1. 弹出层（下拉菜单 / 选择器）
   上游做法：从被按的按钮位置长出来（transform-origin 由 JS 写成 --ox），
   入场 420ms 长尾柔落不回弹，出场 220ms 先慢后快「被吸回去」，
   并且出场时先淡出（opacity 在整个时长里下降）再收，看起来像收进按钮。
   -------------------------------------------------------------------------- */
.mp-pop {
  transform-origin: var(--mp-ox, 24px) top;
  animation: mp-pop-fade .18s ease-out, mp-pop-in .42s cubic-bezier(.16, 1, .3, 1);
}
.mp-pop.mp-up { transform-origin: var(--mp-ox, 24px) bottom; --mp-pop-dy: 6px; }
.mp-pop.mp-leaving {
  pointer-events: none;
  animation: mp-pop-out .22s cubic-bezier(.4, 0, .7, .2) forwards;
}
@keyframes mp-pop-fade { from { opacity: 0; } }
@keyframes mp-pop-in { from { transform: translateY(var(--mp-pop-dy, -6px)) scale(.94); } }
@keyframes mp-pop-out { to { opacity: 0; transform: translateY(calc(var(--mp-pop-dy, -6px) * .6)) scale(.96); } }

/* ---------------------------------------------------------------------------
   2. 滑动滑块（分段控件 / 顶部导航）
   关键不是滑，是「一个滑块在停靠站之间滑」——每项自己不发光。
   JS 只需设置 transform: translateX() 与 width（见 snippets 里的 slide()）。
   280ms + cubic-bezier(.22,1,.36,1)。宽和位置同时过渡，短标签才不抖。
   -------------------------------------------------------------------------- */
.mp-seg { position: relative; display: inline-flex; padding: 2px; border-radius: 8px; }
.mp-seg > .mp-thumb {
  position: absolute; top: 2px; bottom: 2px; left: 0; width: 0;
  border-radius: 6px; background: var(--mp-seg-thumb); box-shadow: var(--mp-seg-edge);
  transition: transform .28s var(--mp-ease), width .28s var(--mp-ease), opacity .15s;
  pointer-events: none;
}
.mp-seg > .mp-thumb.mp-still { transition: none; }   /* 首次落位不要动画 */
.mp-seg > button {
  position: relative; z-index: 1; height: 24px; padding: 0 12px; border-radius: 6px;
  font-size: 12.5px; color: var(--mp-ctl-fg); background: none; border: 0;
  transition: color .18s var(--mp-ease), transform .14s var(--mp-ease);
}
.mp-seg > button.mp-on { color: var(--mp-fg); }

/* ---------------------------------------------------------------------------
   3. 图标竖栏的滑块（选择器左侧的分类栏）
   260ms / cubic-bezier(.22,1,.36,1) 的位移 + 图标本身 1.04 倍微放大。
   悬停提示条：90ms 淡入 + 140ms 位移，visibility 用 delay 收尾，
   这样淡出时不会闪。
   -------------------------------------------------------------------------- */
.mp-rail { position: relative; display: flex; flex-direction: column; gap: 3px; padding: 6px 5px; width: 43px; }
.mp-rail > .mp-rail-thumb {
  position: absolute; z-index: 0; top: 0; left: 5px; width: 31px; height: 31px; border-radius: 8px;
  opacity: 0; background: var(--mp-sel);
  box-shadow: inset 0 0 0 1px var(--mp-line-2), 0 1px 2px rgba(0, 0, 0, .06);
  transition: transform 260ms cubic-bezier(.22, 1, .36, 1), opacity 100ms ease-out;
  pointer-events: none; will-change: transform;
}
.mp-rail > button {
  position: relative; z-index: 1; width: 31px; height: 31px; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--mp-muted); background: none; border: 0;
  transition: color 150ms ease-out, transform 150ms cubic-bezier(.25, 1, .5, 1);
}
.mp-rail > button:active { transform: scale(.94); }
.mp-rail > button svg { transition: transform 220ms cubic-bezier(.22, 1, .36, 1); }
.mp-rail > button.mp-on svg { transform: scale(1.04); }
.mp-rail-tip {
  position: fixed; z-index: 31; top: 0; left: 0; transform: translate(-3px, -50%);
  padding: 3px 8px; border-radius: 6px; background: var(--mp-fg); color: var(--mp-bg);
  font-size: 11.5px; font-weight: 500; line-height: 16px; white-space: nowrap;
  pointer-events: none; opacity: 0; visibility: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .18);
  transition: opacity 90ms ease-out, transform 140ms cubic-bezier(.22, 1, .36, 1), visibility 0s 90ms;
}
.mp-rail-tip.mp-on { opacity: 1; visibility: visible; transform: translate(0, -50%); transition: opacity 90ms ease-out, transform 140ms cubic-bezier(.22, 1, .36, 1); }

/* ---------------------------------------------------------------------------
   4. 标志小鸟（brand 的彩蛋）
   把一只鸟的 SVG 拆成 body / tail / wing 三个 clipPath，各自绕自己的关节点转，
   三段相位错开的衰减振荡（16° → -7° → 9° → -3° → 2°），900ms 一次。
   要点：transform-box: view-box + 显式 transform-origin，否则旋转中心是全图。
   -------------------------------------------------------------------------- */
.mp-bird .mp-parts, .mp-bird.mp-wag .mp-whole { display: none; }
.mp-bird.mp-wag .mp-parts { display: inline; }
.mp-bird .mp-tail { transform-box: view-box; transform-origin: 14px 22px; }
.mp-bird .mp-wing { transform-box: view-box; transform-origin: 30px 19.8px; }
/* 整体「点头」转的是最外层 <g class="mp-bird"> 自己，不是某一块 */
.mp-bird { transform-box: view-box; transform-origin: 26px 38px; }
.mp-bird.mp-wag .mp-tail { animation: mp-tail-flick .9s cubic-bezier(.3, 0, .3, 1); }
.mp-bird.mp-wag .mp-wing { animation: mp-wing-flap .9s cubic-bezier(.3, 0, .3, 1); }
.mp-bird.mp-wag { animation: mp-bird-bob .9s cubic-bezier(.3, 0, .3, 1); }
@keyframes mp-wing-flap {
  0%, 100% { transform: none; } 12% { transform: rotate(10deg); } 26% { transform: rotate(-3deg); }
  40% { transform: rotate(8deg); } 56% { transform: rotate(-2deg); } 72% { transform: rotate(3deg); }
}
@keyframes mp-tail-flick {
  0%, 100% { transform: none; } 18% { transform: rotate(16deg); } 36% { transform: rotate(-7deg); }
  54% { transform: rotate(9deg); } 72% { transform: rotate(-3deg); } 86% { transform: rotate(2deg); }
}
@keyframes mp-bird-bob {
  0%, 100% { transform: none; } 18% { transform: rotate(-4deg); } 40% { transform: rotate(2deg); } 62% { transform: rotate(-1deg); }
}

/* ---------------------------------------------------------------------------
   5. 路由舞台：虚线流动 + 会扇翅膀的运载物
   wires 用 stroke-dasharray + dashoffset 线性循环（唯一的 linear，因为它表示「持续流动」，
   不是「一次响应」）；运载物是 pouched 小鸟：远翅延迟 -30ms，看起来才有前后景。
   -------------------------------------------------------------------------- */
.mp-wire path { fill: none; stroke: var(--mp-line); stroke-width: 1.5; transition: opacity .3s; }
.mp-wire path.mp-live { stroke: var(--mp-agent, var(--mp-muted)); stroke-dasharray: 3 5; animation: mp-flow .7s linear infinite; }
.mp-wire path.mp-rest { stroke-dasharray: 2 6; opacity: .45; }
@keyframes mp-flow { to { stroke-dashoffset: -8; } }

.mp-pkt { fill: var(--mp-agent, var(--mp-fg)); filter: drop-shadow(0 0 3px var(--mp-agent, var(--mp-muted))); }
.mp-pkt.mp-back { fill: var(--mp-card); stroke: var(--mp-agent, var(--mp-fg)); stroke-width: 2.25; }
.mp-pkt.mp-back.mp-err { stroke: var(--mp-red); }
.mp-pkt.mp-held { animation: mp-held 1.2s ease-in-out infinite; }
@keyframes mp-held { 50% { opacity: .45; } }

/* ---------------------------------------------------------------------------
   6. 一次性的「点到了」反馈
   tick：40% 处放大 1.04（不是从头到尾，所以看起来是「被撞了一下」）
   hit：左右衰减 5→4→2→1px，像被轻撞
   pop：复制成功时 0.55 → 1.16 → 1，一次 360ms
   -------------------------------------------------------------------------- */
.mp-tick { animation: mp-tick .4s ease-out; }
@keyframes mp-tick { 40% { transform: scale(1.04); } }
.mp-hit { animation: mp-hit .45s; }
@keyframes mp-hit { 20% { transform: translateX(-5px); } 40% { transform: translateX(4px); } 60% { transform: translateX(-2px); } 80% { transform: translateX(1px); } }
.mp-copy-pop { animation: mp-copy-pop .36s var(--mp-ease); }
@keyframes mp-copy-pop { 0% { transform: scale(.55); } 55% { transform: scale(1.16); } 100% { transform: scale(1); } }

/* ---------------------------------------------------------------------------
   7. 活着：正在服务的指示灯 / 骨架屏
   live：0 → 7px 扩散环，1.8s 无限（表示状态，不是加载）
   skel：渐变条横移，明暗对比极低，用来占位而不是抢注意力
   -------------------------------------------------------------------------- */
.mp-live { box-shadow: 0 0 0 0 color-mix(in srgb, var(--mp-fg) 40%, transparent); animation: mp-live 1.8s infinite; }
@keyframes mp-live { 70% { box-shadow: 0 0 0 7px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } }
.mp-skel {
  background: linear-gradient(90deg, var(--mp-pill) 25%, var(--mp-card-2) 50%, var(--mp-pill) 75%);
  background-size: 200% 100%;
  animation: mp-skel-shift 1.4s linear infinite;
}
@keyframes mp-skel-shift { from { background-position: 100% 0; } to { background-position: -100% 0; } }

/* ---------------------------------------------------------------------------
   8. 视图切换入场
   只 5px 上浮 + 淡入，240ms。macOS 原生页签就是这个量级；
   再大就成了「转场秀」，切 tab 会烦。
   -------------------------------------------------------------------------- */
.mp-view-in { animation: mp-view-in .24s var(--mp-ease); }
@keyframes mp-view-in { from { opacity: 0; transform: translateY(5px); } }

/* ---------------------------------------------------------------------------
   全局兜底：三条 reduced-motion 规则（上游逐处写的，这里合并成一处）
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .mp-pop { animation: mp-pop-fade .12s ease-out; }
  .mp-pop.mp-leaving { animation: none; }
  .mp-wire path.mp-live, .mp-tick, .mp-hit, .mp-copy-pop, .mp-bird.mp-wag .mp-tail,
  .mp-bird.mp-wag .mp-wing, .mp-bird.mp-wag, .mp-skel { animation: none; }
  .mp-rail > .mp-rail-thumb, .mp-rail-tip, .mp-view-in { transition: none; animation: none; }
}
