/* ============================================================================
   隔空投送（Airdrop · Beta）—— 样式与动画
   ----------------------------------------------------------------------------
   设计前提：这个功能卖的就是"传送的观感"，所以视觉重心全部压在中央那个
   三节点舞台上：

       [ 我 ] ──上传──▶ [ 存储节点 ] ──下载──▶ [ 对方 ]

   为什么要显式画出中间的"存储节点"：真实链路就是两段（发端上传到中转，
   收端再从中转下载），把它画出来，上传/下载两个阶段各自卡在哪儿一眼可见，
   而不是假装两台设备直连、然后让用户在"正在传输…"里干等。

   动画原则（与全站一致，勿自造曲线）：
     · 结构性变化（节点点亮、连线展开）用 transition + 状态 class 驱动
     · 循环性效果（光流、呼吸、脉冲）才用 @keyframes
     · 只动 transform / opacity，不碰宽高与布局属性
   减弱动效由 design-system.css 全局兜底，本文件不再重复写媒体查询。
   ============================================================================ */

/* ── 面板 ─────────────────────────────────────────────────
   尺寸比 .modal--dialog 大：舞台要横向铺开三节点，480px 会挤成一团。
   入场动画不在这里写 —— 交给 <transition name="dialog">，两套曲线争同一条
   transform 轨道会抖。 */
.ad-modal {
  /* 舞台（三节点 + 轨道）移除后不再需要 680px 的横向空间：
     传送指示卡只有 300px，面板收到 420px，整块更接近一张卡片而不是一个面板。 */
  width: min(420px, 94vw);
  max-height: 92vh;
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  /* 定位基准：.ad-cloudwin-mask 用 absolute inset:0 落在面板内部。
     .modal 本身没有 position，这里必须显式声明，否则遮罩会一路
     冒泡到更外层的定位祖先，跑到面板以外的位置。 */
  position: relative;
  /* 禁止整个隔空投送面板滚动 / 出现滚动条：它应该是一张固定的卡片。
     继承自 .modal 的 overflow-y: auto 会在这里产生滚动条，并导致
     PC 上「组件 UI 会动」的观感。 */
  overflow: hidden;
}

.ad-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex: none;
}

.ad-head__title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--fs-lg);
  font-weight: 800;
  letter-spacing: -0.01em;
}

.ad-head__title i {
  color: var(--primary);
}

.ad-head__spacer {
  flex: 1;
}

/* 房间倒计时徽标（头部右侧）。
   房间是"5 分钟内必须走完"的短命资源，把剩余时间摆在标题栏，
   用户随时能看到还剩多久；进入最后一分钟转警示色。
   数字用等宽字形，避免每秒变化时宽度抖动导致标题栏跳动。 */
.ad-countdown {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: none;
  padding: 3px 9px;
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  color: var(--text-muted);
  background: rgba(148, 163, 184, 0.14);
  border: 1px solid rgba(148, 163, 184, 0.24);
  transition: color var(--dur-fast) var(--ease-apple),
              background var(--dur-fast) var(--ease-apple),
              border-color var(--dur-fast) var(--ease-apple);
}

.ad-countdown.is-urgent {
  color: #b91c1c;
  background: rgba(239, 68, 68, 0.12);
  border-color: rgba(239, 68, 68, 0.3);
  animation: adCountdownPulse 1s var(--ease-apple) infinite;
}

/* 最后一分钟的轻微呼吸，不打断阅读但足以引起注意 */
@keyframes adCountdownPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.62; }
}

/* BETA 徽章：全站此前没有，这里一并建起来 */
.ad-beta {
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: 0.06em;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: linear-gradient(135deg, rgba(var(--primary-rgb), 0.18), rgba(139, 92, 246, 0.16));
  color: var(--primary-dark);
  border: 1px solid rgba(var(--primary-rgb), 0.32);
  animation: adPop 0.28s var(--ease-apple) backwards;
}

/* 元素"弹出来"：BETA 徽章入场、节点完成时打勾图标。
   此前只写了 animation 却漏了关键帧定义，动画一直静默失效（引用不存在的
   关键帧不报错，就是不播）。只动 transform / opacity，回弹交给调用方的
   --ease-spring，关键帧本身只负责 0.6 → 1 的放大。 */
@keyframes adPop {
  from {
    opacity: 0;
    transform: scale(0.6);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

html[data-theme='dark'] .ad-beta {
  color: var(--primary-light);
  border-color: rgba(var(--primary-rgb), 0.4);
}

/* ── 主体 ──────────────────────────────────────────────── */
.ad-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* 内容超高时必须能滚 —— 不能再用 center。
     center 的作用是「内容比容器矮时居中」，可一旦内容比容器高，flex 居中会让
     溢出部分**同时向上下两头跑**，而上方那段滚不到（滚动条起点在 0，负偏移区域
     永远够不着）。小屏手机上「选文件后再也点不到底部按钮」就是这么来的：
     底部内容被裁在容器之下 + 上方被裁的部分滚不回来。
     改用 flex-start，超高时从顶部正常排布、可完整滚到底。 */
  justify-content: flex-start;
  gap: var(--sp-4);
  /* 面板本身仍不滚（见 .ad-modal），滚动收口到这一层，
     于是 .ad-foot 固定在视口内、始终可点。 */
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  /* 滚动到底部时给内容一点呼吸，避免最后一个控件贴着边缘 */
  padding-bottom: var(--sp-1);
}

/* 内容不多时再居中 —— 用 margin:auto 而非 justify-content:center：
   它只在有剩余空间时生效，容器被撑满时自动让位，不会制造不可达的负偏移。 */
.ad-body > :first-child { margin-top: auto; }
.ad-body > :last-child { margin-bottom: auto; }

/* 滚动条在移动端是纯噪音，隐藏（与 .ad-files__list 一致） */
.ad-body { scrollbar-width: none; -ms-overflow-style: none; }
.ad-body::-webkit-scrollbar { display: none; }

/* ── 角色选择 ──────────────────────────────────────────── */
.ad-roles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-3);
  width: 100%;
  max-width: 420px;
}

.ad-role {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: var(--sp-5) var(--sp-3);
  border-radius: var(--r-lg);
  border: 1.5px solid var(--glass-border-ice);
  background: var(--glass-subtle);
  cursor: pointer;
  overflow: hidden;
  transition:
    transform var(--dur-base) var(--ease-spring),
    border-color var(--dur-base) var(--ease-apple),
    background var(--dur-base) var(--ease-apple),
    box-shadow var(--dur-slow) var(--ease-apple);
}

.ad-role:hover {
  transform: translateY(-2px);
  border-color: rgba(var(--primary-rgb), 0.5);
}

.ad-role:active {
  transform: scale(0.985);
}

.ad-role.is-active {
  border-color: var(--primary);
  background: rgba(var(--primary-rgb), 0.08);
  box-shadow: var(--glow-primary), var(--shadow-soft);
}

.ad-role__icon {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 1.05rem;
  /* 与舞台节点同一套语言：低透明度色晕 + 发丝描边，不做实心渐变块。
     入口处若用饱和大色块，会和后面"精致"的舞台打架。 */
  color: var(--primary);
  background: rgba(var(--primary-rgb), 0.09);
  border: 1px solid rgba(var(--primary-rgb), 0.28);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
  transition: transform var(--dur-slow) var(--ease-spring);
}

.ad-role:hover .ad-role__icon {
  transform: scale(1.08) rotate(-4deg);
}

/* 接收卡图标：同一套"细描边 + 10% 色晕"换成功色，
   不再使用内联的实心绿渐变（旧版会跟发送卡打架，两只色块很土） */
.ad-role__icon--recv {
  color: var(--c-success);
  background: rgba(16, 185, 129, 0.09);
  border-color: rgba(16, 185, 129, 0.28);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
}

.ad-role__label {
  font-size: var(--fs-md);
  font-weight: 800;
}

.ad-role__hint {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  text-align: center;
  line-height: 1.5;
}

/* ── 传送指示：环形读数 + 上传/下载两条腿 ───────────────────
   取代原先「三节点舞台 + 飞行文件包」：那个版本横向要 460px、动画面积大，
   观感偏玩具。这里压成一张紧凑指示卡——左边环形读数，右边上传 / 下载两条
   腿各自点亮，形态接近地铁乘车码的状态区：一眼看出"哪一段在动、走到多少"。

   配色沿用全站的克制语言：1px 发丝描边 + 10% 色晕，不做饱和实心块。 */
.ad-flow {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  width: 100%;
  max-width: 300px;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-lg);
  border: 1px solid var(--glass-border-ice);
  background: var(--glass-subtle);
  animation: adRise 0.36s var(--ease-out) backwards;
}

.ad-flow__ring {
  position: relative;
  flex: none;
  width: 68px;
  height: 68px;
  display: grid;
  place-items: center;
}

.ad-ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}

.ad-ring__bg {
  fill: none;
  stroke: rgba(148, 163, 184, 0.18);
  /* 3.5px：环是"读数"不是"装饰圈"，细了才像仪表，粗了就成了色块 */
  stroke-width: 3.5;
}

.ad-ring__val {
  fill: none;
  stroke: var(--primary);
  stroke-width: 3.5;
  stroke-linecap: round;
  /* 半径 26 → 周长约 163.4 */
  stroke-dasharray: 163.4;
  stroke-dashoffset: calc(163.4 - 163.4 * var(--ad-p, 0));
  transition: stroke-dashoffset 0.18s linear, stroke var(--dur-base) var(--ease-apple);
}

/* 完成态：环整体转成功色，中心换勾——不再需要额外一块大勾 SVG */
.ad-flow__ring.is-done .ad-ring__val {
  stroke: var(--c-success);
}

.ad-flow__core {
  position: relative;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 1px;
  color: var(--primary-dark);
  font-variant-numeric: tabular-nums;
}

.ad-flow__pct {
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1;
}

.ad-flow__unit {
  font-size: 0.62rem;
  font-weight: 700;
  opacity: 0.65;
}

.ad-flow__core i {
  font-size: 1.05rem;
  color: var(--text-muted);
}

.ad-flow__ring.is-done .ad-flow__core i {
  color: var(--c-success);
  animation: adPop 0.32s var(--ease-spring) backwards;
}

/* 传输中：环外一圈极淡的呼吸，表示"在动"但不抢戏 */
.ad-flow__ring.is-live::after {
  content: '';
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  border: 1px solid rgba(var(--primary-rgb), 0.22);
  animation: adBreathe 1.6s var(--ease-apple) infinite;
}

@keyframes adBreathe {
  0%, 100% { opacity: 0.35; transform: scale(0.97); }
  50% { opacity: 0.9; transform: scale(1.03); }
}

html[data-theme='dark'] .ad-flow__core {
  color: var(--primary-light);
}

.ad-flow__legs {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.ad-flow__leg {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 5px 8px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  transition:
    background var(--dur-base) var(--ease-apple),
    border-color var(--dur-base) var(--ease-apple);
}

.ad-flow__leg-icon {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 0.7rem;
  color: var(--text-muted);
  background: rgba(148, 163, 184, 0.1);
  border: 1px solid var(--glass-border-ice);
  transition:
    color var(--dur-base) var(--ease-apple),
    background var(--dur-base) var(--ease-apple),
    border-color var(--dur-base) var(--ease-apple);
}

.ad-flow__leg-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.ad-flow__leg-text b {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text-muted);
  transition: color var(--dur-base) var(--ease-apple);
}

.ad-flow__leg-text small {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  opacity: 0.72;
}

/* 进行中：细描边 + 10% 色晕，和全站同一套语言 */
.ad-flow__leg.is-live {
  background: rgba(var(--primary-rgb), 0.07);
  border-color: rgba(var(--primary-rgb), 0.26);
}

.ad-flow__leg.is-live .ad-flow__leg-icon {
  color: var(--primary);
  background: rgba(var(--primary-rgb), 0.12);
  border-color: rgba(var(--primary-rgb), 0.34);
  animation: adLegPulse 1.5s var(--ease-apple) infinite;
}

.ad-flow__leg.is-live .ad-flow__leg-text b {
  color: var(--text-main);
}

@keyframes adLegPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(var(--primary-rgb), 0); }
  50% { box-shadow: 0 0 0 4px rgba(var(--primary-rgb), 0.1); }
}

/* 已完成：成功色细描边 */
.ad-flow__leg.is-done .ad-flow__leg-icon {
  color: var(--c-success);
  background: rgba(16, 185, 129, 0.1);
  border-color: rgba(16, 185, 129, 0.3);
}

.ad-flow__leg.is-done .ad-flow__leg-text b {
  color: var(--text-main);
}

/* ── 二维码与连接码 ────────────────────────────────────── */
.ad-code {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
}

.ad-qr-wrap {
  position: relative;
  padding: 10px;
  border-radius: var(--r-lg);
  background: #fff;
  border: 1px solid rgba(var(--primary-rgb), 0.3);
  box-shadow: 0 10px 28px -10px rgba(2, 132, 199, 0.35);
  /* 呼吸：让"等待扫描"这件事本身有生命感，而不是一张静止的图 */
  animation: adBreath 3.2s var(--ease-apple) infinite;
}

/* 二维码必须白底深码。扫描器对反色码兼容性参差，跟着主题换色会让一部分
   手机直接扫不出来 —— 这是可用性要求，不是配色偏好。 */
.ad-qr {
  width: 158px;
  height: 158px;
  color: #0f172a;
  display: block;
}

.ad-qr svg {
  display: block;
  width: 100%;
  height: 100%;
}

@keyframes adBreath {
  0%,
  100% {
    transform: translateY(0);
    box-shadow: 0 10px 28px -10px rgba(2, 132, 199, 0.35);
  }
  50% {
    transform: translateY(-3px);
    box-shadow: 0 16px 34px -10px rgba(2, 132, 199, 0.5);
  }
}

/* 8 位连接码：逐字入场，等宽 + 大字距，方便口头念和手打 */
.ad-digits {
  display: flex;
  gap: 6px;
}

.ad-digit {
  min-width: 30px;
  height: 38px;
  padding: 0 4px;
  border-radius: var(--r-xs);
  display: grid;
  place-items: center;
  font-size: 1.15rem;
  font-weight: 800;
  letter-spacing: 0;
  color: var(--primary-dark);
  background: rgba(var(--primary-rgb), 0.09);
  border: 1px solid rgba(var(--primary-rgb), 0.22);
  font-variant-numeric: tabular-nums;
  animation: adDigitIn 0.42s var(--ease-fluid) backwards;
}

html[data-theme='dark'] .ad-digit {
  color: var(--primary-light);
}

@keyframes adDigitIn {
  from {
    opacity: 0;
    transform: translateY(8px) scale(0.86);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.ad-code__hint {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  text-align: center;
  line-height: 1.6;
  max-width: 320px;
}

/* ── 输入连接码 ────────────────────────────────────────── */
.ad-join {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  max-width: 340px;
}

.ad-code-input {
  width: 100%;
  height: var(--h-ctrl-lg);
  text-align: center;
  font-size: 1.25rem;
  font-weight: 800;
  letter-spacing: 0.32em;
  text-indent: 0.32em;
  text-transform: uppercase;
  border-radius: var(--r-md);
  border: 1.5px solid var(--glass-border-ice);
  background: var(--glass-subtle);
  color: var(--text-main);
  transition: border-color var(--dur-base) var(--ease-apple), box-shadow var(--dur-base) var(--ease-apple);
}

.ad-code-input:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.18);
}

.ad-code-input::placeholder {
  color: var(--text-muted);
  font-weight: 600;
  letter-spacing: 0.2em;
}

/* 接收端「加入房间」阶段的退路：尚未建立会话，直接退回角色选择。
   之前这里没有任何出口，进入接收端就卡死、无法切换成发端。 */
.ad-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: var(--sp-1);
  padding: 6px 12px;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  border-radius: var(--r-pill);
  transition: color var(--dur-base) var(--ease-apple), background var(--dur-base) var(--ease-apple);
}

.ad-back:hover {
  color: var(--primary);
  background: rgba(var(--primary-rgb), 0.08);
}

.ad-back:active {
  transform: scale(0.98);
}

.ad-back i {
  font-size: 0.85em;
}

/* ── 动作区 ────────────────────────────────────────────── */
.ad-actions {
  display: flex;
  gap: var(--sp-2);
  width: 100%;
  max-width: 420px;
  justify-content: center;
  flex-wrap: wrap;
}

.ad-btn {
  flex: 1 1 150px;
  min-height: var(--h-ctrl-lg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 0 var(--sp-4);
  border-radius: var(--r-md);
  border: 1.5px solid var(--glass-border-ice);
  background: var(--glass-subtle);
  color: var(--text-main);
  font-size: var(--fs-md);
  font-weight: 700;
  cursor: pointer;
  transition:
    transform var(--dur-fast) var(--ease-press),
    background var(--dur-base) var(--ease-apple),
    border-color var(--dur-base) var(--ease-apple),
    box-shadow var(--dur-base) var(--ease-apple);
}

.ad-btn:hover:not(:disabled) {
  border-color: rgba(var(--primary-rgb), 0.45);
  background: rgba(var(--primary-rgb), 0.07);
}

.ad-btn:active:not(:disabled) {
  transform: scale(0.98);
}

.ad-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.ad-btn--primary {
  color: #fff;
  background: linear-gradient(140deg, var(--primary-light), var(--primary));
  border-color: transparent;
  box-shadow: 0 8px 20px -8px rgba(var(--primary-rgb), 0.55);
}

.ad-btn--primary:hover:not(:disabled) {
  background: linear-gradient(140deg, var(--primary), var(--primary-dark));
  border-color: transparent;
}

/* ── 状态文案 ──────────────────────────────────────────── */
.ad-status {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  text-align: center;
  min-height: 46px;
  justify-content: center;
}

.ad-status__title {
  font-size: var(--fs-lg);
  font-weight: 800;
  letter-spacing: -0.01em;
  animation: adRise 0.4s var(--ease-out) backwards;
}

.ad-status__sub {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  animation: adRise 0.46s var(--ease-out) 0.04s backwards;
}

@keyframes adRise {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* 等待中的三点省略号 */
.ad-dots i {
  display: inline-block;
  width: 4px;
  height: 4px;
  margin: 0 2px;
  border-radius: 50%;
  background: var(--text-muted);
  animation: adDot 1.2s var(--ease-apple) infinite;
}

.ad-dots i:nth-child(2) {
  animation-delay: 0.15s;
}

.ad-dots i:nth-child(3) {
  animation-delay: 0.3s;
}

@keyframes adDot {
  0%,
  60%,
  100% {
    opacity: 0.25;
    transform: translateY(0);
  }
  30% {
    opacity: 1;
    transform: translateY(-3px);
  }
}

/* ── 提示条 ────────────────────────────────────────────── */
.ad-note {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  width: 100%;
  max-width: 420px;
  padding: var(--sp-3);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  line-height: 1.6;
  background: rgba(var(--primary-rgb), 0.07);
  border: 1px solid rgba(var(--primary-rgb), 0.2);
  color: var(--text-sub);
  animation: adRise 0.36s var(--ease-out) backwards;
}

.ad-note i {
  flex: none;
  margin-top: 2px;
  color: var(--primary);
}

.ad-note--warn {
  background: rgba(245, 158, 11, 0.1);
  border-color: rgba(245, 158, 11, 0.32);
}

.ad-note--warn i {
  color: var(--c-warn);
}

.ad-note--error {
  background: var(--c-danger-soft);
  border-color: rgba(225, 29, 72, 0.3);
}

.ad-note--error i {
  color: var(--c-danger);
}

/* ── 已选文件摘要 ──────────────────────────────────────── */
.ad-file {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  max-width: 420px;
  padding: var(--sp-3);
  border-radius: var(--r-md);
  border: 1.5px solid var(--glass-border-ice);
  background: var(--glass-subtle);
  animation: adRise 0.36s var(--ease-out) backwards;
}

.ad-file__icon {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: var(--r-xs);
  display: grid;
  place-items: center;
  font-size: 1.05rem;
  color: var(--primary);
  background: rgba(var(--primary-rgb), 0.12);
}

.ad-file__meta {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.ad-file__name {
  font-size: var(--fs-md);
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ad-file__size {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin-top: 2px;
}

/* ── 云端选取窗口（独立覆盖层）───────────────────────────
   早期把列表内联在主面板里，小屏下被 .ad-body 的 flex 纵向布局压成一条缝。
   独立成覆盖层后高度由自己决定，不再参与主面板的 flex 分配。 */
.ad-cloudwin-mask {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-4);
  background: rgba(15, 23, 42, 0.42);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border-radius: inherit;
}

.ad-cloudwin {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 460px;
  /* 用 dvh 而非 vh：移动端浏览器地址栏收放时 vh 不会变，会算高导致
      footer 被挤出可视区。dvh 跟随可视高度，新旧浏览器都优雅降级。 */
  max-height: min(72vh, 72dvh);
  min-height: 0;
  border-radius: var(--r-lg);
  border: 1px solid var(--glass-border-ice);
  background: var(--glass-surface);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.24);
  overflow: hidden;
}

.ad-cloudwin__head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 12px var(--sp-3);
  border-bottom: 1px solid var(--glass-border-ice);
  flex: none;
}

.ad-cloudwin__title {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-md);
  font-weight: 700;
}

/* 列表区：flex:1 + min-height:0 才能正确滚动 —— 少了 min-height:0，
   flex 子项的默认 min-height:auto 会让它撑开、把 footer 顶出容器。 */
.ad-cloudwin__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.ad-cloudwin__hint {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-6) var(--sp-4);
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.ad-cloudwin__row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  padding: 11px var(--sp-4);
  border: none;
  background: transparent;
  cursor: pointer;
  text-align: left;
  color: inherit;
  transition: background var(--dur-fast) var(--ease-apple);
}

.ad-cloudwin__row:hover {
  background: rgba(var(--primary-rgb), 0.07);
}

.ad-cloudwin__row + .ad-cloudwin__row {
  border-top: 1px solid rgba(148, 163, 184, 0.16);
}

.ad-cloudwin__row .fa-file {
  color: var(--primary);
}

.ad-cloudwin__row.is-picked {
  background: rgba(16, 185, 129, 0.1);
}

.ad-cloudwin__row.is-picked i {
  color: var(--c-success);
}

.ad-cloudwin__name {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-sm);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ad-cloudwin__size {
  flex: none;
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.ad-cloudwin__foot {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 12px var(--sp-4);
  border-top: 1px solid var(--glass-border-ice);
  flex: none;
}

.ad-cloudwin__count {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* 内部滚动容器（云端列表、已选文件列表）仍可在溢出时滚动，
   但隐藏原生日程条：PC 上滚动条会破坏「固定卡片」的观感。 */
.ad-cloudwin__body,
.ad-files__list {
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE/Edge legacy */
}

.ad-cloudwin__body::-webkit-scrollbar,
.ad-files__list::-webkit-scrollbar {
  display: none; /* Chrome/Safari/Edge */
}

/* ── 底部 ──────────────────────────────────────────────── */
.ad-foot {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* ── 打磨增强：连接脉冲 / 复制反馈 / 扫描取景框 / 文件行 ─────────────
   这里集中放置后加的观感增强，规则与上文一致：
     · 结构性变化走 transition + 状态 class；
     · 循环/一次性效果才用 @keyframes；
     · 只动 transform / opacity / 阴影，不碰布局属性。 */

/* 连接成功的双环涟漪。⚠️ linkPulse 这个状态位存在了很久却一直没有任何
   CSS 对应它 —— 脉冲从来没播出过（与 adPop 缺关键帧是同型静默失效）。
   收端 join 成功、发端看到对方接入时各播一次。 */
.ad-flow.is-pulse .ad-flow__ring::before,
.ad-flow.is-pulse .ad-flow__ring::after {
  content: '';
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 1.5px solid rgba(var(--primary-rgb), 0.55);
  animation: adLinkRipple 1.5s var(--ease-out) forwards;
  pointer-events: none;
}

.ad-flow.is-pulse .ad-flow__ring::after {
  animation-delay: 0.18s;
}

@keyframes adLinkRipple {
  0% { opacity: 0.9; transform: scale(0.9); }
  100% { opacity: 0; transform: scale(1.9); }
}

/* 脉冲期间卡片整体亮一下主色描边，和涟漪同节拍 */
.ad-flow.is-pulse {
  border-color: rgba(var(--primary-rgb), 0.5);
  box-shadow: 0 0 0 4px rgba(var(--primary-rgb), 0.12);
  transition: border-color var(--dur-slow) var(--ease-apple),
              box-shadow var(--dur-slow) var(--ease-apple);
}

/* 失败态：卡片与环一起转红，比单独一行红字更"收束" */
.ad-flow.is-failed {
  border-color: rgba(225, 29, 72, 0.35);
}

.ad-flow.is-failed .ad-ring__val {
  stroke: var(--c-danger);
}

/* 传输中给进度环加一圈微光：读数在动的事实一眼可见 */
.ad-flow__ring.is-live .ad-ring__val {
  filter: drop-shadow(0 0 3px rgba(var(--primary-rgb), 0.55));
}

/* 完成时腿部的图标弹跳一下，事件感落在"哪一段完成了"上 */
.ad-flow__leg.is-done .ad-flow__leg-icon {
  animation: adPop 0.3s var(--ease-spring) backwards;
}

/* 二维码：hover 微微抬起，明示"可点（复制）"；复制成功整块短暂转成功色。
   ⚠️ wrap 自身的 transform 被 adBreath 呼吸动画占着（动画帧优先级高于
   transition），所以 hover 的位移只能落在内层 .ad-qr 上。 */
.ad-qr-wrap {
  cursor: pointer;
  transition: border-color var(--dur-base) var(--ease-apple);
}

.ad-qr {
  transition: transform var(--dur-base) var(--ease-spring);
}

.ad-qr-wrap:hover .ad-qr {
  transform: scale(1.03);
}

.ad-qr-wrap:active .ad-qr {
  transform: scale(0.98);
}

.ad-qr-wrap.is-copied {
  border-color: rgba(16, 185, 129, 0.55);
}

/* 复制成功的浮起气泡：就地反馈，不需要打断性 toast 也能确认 */
.ad-qr-copied {
  position: absolute;
  top: -8px;
  right: -8px;
  padding: 3px 9px;
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: 800;
  color: #fff;
  background: var(--c-success);
  box-shadow: 0 6px 16px -6px rgba(16, 185, 129, 0.6);
  animation: adPop 0.24s var(--ease-spring) backwards;
  pointer-events: none;
}

.btn.is-copied {
  color: var(--c-success);
  border-color: rgba(16, 185, 129, 0.45);
}

/* 连接码数字格 hover 轻浮：让"可以点码复制"这件事有手感 */
.ad-digit {
  cursor: copy;
  transition: transform var(--dur-fast) var(--ease-spring);
}

.ad-digit:hover {
  transform: translateY(-2px);
}

/* 扫码取景框：四角描边 + 一条往复扫线，替代裸 video。
   四角用八条定位渐变画（每角横竖各一条），不需要额外 DOM。 */
.ad-scanbox {
  position: relative;
  width: 100%;
  max-width: 280px;
  border-radius: var(--r-md);
  overflow: hidden;
  background: #000;
}

.ad-scanbox video {
  display: block;
  width: 100%;
}

.ad-scanbox::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--r-md);
  background:
    linear-gradient(to right, var(--primary) 2.5px, transparent 2.5px) left 0 top 0 / 18px 18px,
    linear-gradient(to bottom, var(--primary) 2.5px, transparent 2.5px) left 0 top 0 / 18px 18px,
    linear-gradient(to left, var(--primary) 2.5px, transparent 2.5px) right 0 top 0 / 18px 18px,
    linear-gradient(to bottom, var(--primary) 2.5px, transparent 2.5px) right 0 top 0 / 18px 18px,
    linear-gradient(to right, var(--primary) 2.5px, transparent 2.5px) left 0 bottom 0 / 18px 18px,
    linear-gradient(to top, var(--primary) 2.5px, transparent 2.5px) left 0 bottom 0 / 18px 18px,
    linear-gradient(to left, var(--primary) 2.5px, transparent 2.5px) right 0 bottom 0 / 18px 18px,
    linear-gradient(to top, var(--primary) 2.5px, transparent 2.5px) right 0 bottom 0 / 18px 18px;
  background-repeat: no-repeat;
  pointer-events: none;
  z-index: 1;
}

/* 扫线做成 22% 高的光带：translateY 的百分比相对自身高度，
   因此取景框高随摄像头画面变化时，扫程始终覆盖全框，不写死像素。 */
.ad-scanbox::after {
  content: '';
  position: absolute;
  left: 10px;
  right: 10px;
  top: 0;
  height: 22%;
  border-radius: 3px;
  background: linear-gradient(to bottom,
    transparent,
    rgba(var(--primary-rgb), 0.3) 38%,
    rgba(var(--primary-rgb), 0.85) 50%,
    rgba(var(--primary-rgb), 0.3) 62%,
    transparent);
  animation: adScanLine 1.9s ease-in-out infinite;
  pointer-events: none;
  z-index: 1;
}

@keyframes adScanLine {
  0%, 100% { transform: translateY(-10%); }
  50% { transform: translateY(455%); }
}

/* 文件行逐个入场（--i 由模板按序号注入） */
.ad-file {
  animation-delay: calc(var(--i, 0) * 45ms);
}

/* 收端「正在接收的这一行」点亮 */
.ad-file.is-live {
  border-color: rgba(var(--primary-rgb), 0.4);
  background: rgba(var(--primary-rgb), 0.06);
}

.ad-file.is-done {
  border-color: rgba(16, 185, 129, 0.35);
}

.ad-file.is-done .ad-file__icon {
  color: var(--c-success);
  background: rgba(16, 185, 129, 0.1);
}

/* 细颗粒文件图标：类型分色（色板全部取自本文件既有用色，不新造色值） */
.ad-file__icon.is-image { color: #7c5cf0; background: rgba(139, 92, 246, 0.12); }
.ad-file__icon.is-video { color: #0284c7; background: rgba(2, 132, 199, 0.1); }
.ad-file__icon.is-audio { color: #0d9488; background: rgba(16, 185, 129, 0.1); }
.ad-file__icon.is-pdf { color: var(--c-danger); background: rgba(225, 29, 72, 0.1); }
.ad-file__icon.is-archive { color: var(--c-warn); background: rgba(245, 158, 11, 0.12); }
.ad-file__icon.is-code { color: #0d9488; background: rgba(16, 185, 129, 0.1); }
.ad-file__icon.is-doc { color: #0284c7; background: rgba(2, 132, 199, 0.1); }
.ad-file__icon.is-sheet { color: var(--c-success); background: rgba(16, 185, 129, 0.1); }
.ad-file__icon.is-slide { color: var(--c-warn); background: rgba(245, 158, 11, 0.12); }

/* 发送按钮：hover 轻浮 + 主按钮阴影加深，保持可按压感 */
.ad-btn--send:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: 0 12px 24px -8px rgba(var(--primary-rgb), 0.6);
}

/* 节点选择卡键盘可达性：焦点环 */
.ad-node-pick:focus-visible,
.ad-btn:focus-visible,
.ad-role:focus-visible {
  outline: 2px solid rgba(var(--primary-rgb), 0.55);
  outline-offset: 2px;
}

/* ── 响应式 ────────────────────────────────────────────── */
@media (max-width: 520px) {
  .ad-modal {
    width: 100vw;
    max-width: 100vw;
    /* 100dvh 是「动态视口高度」——它已经扣掉了移动端浏览器 UI，
       但仍会被 iOS 的 home indicator / 安卓手势条盖住底部。
       用 max-height 兜一层 + 底部 safe-area 内边距，保证 .ad-foot 露在安全区内。 */
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    padding: var(--sp-4);
    padding-bottom: calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
  }

  .ad-qr {
    width: 132px;
    height: 132px;
  }

  .ad-digit {
    min-width: 26px;
    height: 34px;
    font-size: 1rem;
  }

  .ad-roles {
    max-width: 100%;
  }

  /* 小屏上节点改「紧凑两列」。
     注意别改成纵向单列：单列时每张卡全宽、内容又撑开，
     一个节点就吃掉近 1/4 屏高，三个节点直接把文件列表和发送区挤出屏幕。
     两列网格把宽度对半分，卡片自然变矮，竖向空间留给真正要操作的部分。 */
  .ad-nodes__row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-2);
  }

  .ad-node-pick {
    flex: none;
    width: 100%;
    /* 触控目标不矮于 44px（Apple HIG） */
    min-height: var(--h-ctrl-lg);
    padding: var(--sp-2) var(--sp-3);
    gap: 0;
  }

  /* 卡片变窄后，长节点名（如「Cloudflare R2」）会折行把卡片撑高、做高矮不齐。
     截断成单行，卡片高度就可预期了。 */
  .ad-node-pick span {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* 文件列表在小屏上收得更矮，把空间让给底部操作区 */
  .ad-files__list {
    max-height: 30vh;
  }

  /* 底部栏在窄屏可能换行（节点信息 + 操作按钮挤一行会溢出） */
  .ad-foot {
    flex-wrap: wrap;
    row-gap: var(--sp-2);
  }
}

/* 特小屏（如 iPhone SE 横屏、小屏安卓）：进一步压掉非必要间距 */
@media (max-width: 380px) {
  .ad-modal {
    padding: var(--sp-3);
    padding-bottom: calc(var(--sp-3) + env(safe-area-inset-bottom, 0px));
    gap: var(--sp-3);
  }

  .ad-body {
    gap: var(--sp-3);
  }
}

/* ── 中转节点选择器（发端）────────────────────────────────── */
.ad-nodes {
  width: 100%;
  max-width: 460px;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  animation: adRise 0.34s var(--ease-out) backwards;
}

.ad-nodes__label {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--text-muted);
  text-transform: uppercase;
}

.ad-nodes__row {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

.ad-node-pick {
  flex: 1 1 140px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  border: 1.5px solid var(--glass-border-ice);
  background: var(--glass-subtle);
  color: var(--text-main);
  cursor: pointer;
  text-align: left;
  transition:
    transform var(--dur-fast) var(--ease-press),
    border-color var(--dur-base) var(--ease-apple),
    background var(--dur-base) var(--ease-apple),
    box-shadow var(--dur-base) var(--ease-apple);
}

.ad-node-pick i {
  font-size: 1.1rem;
  color: var(--primary);
}

.ad-node-pick span {
  font-size: var(--fs-md);
  font-weight: 800;
}

.ad-node-pick small {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.ad-node-pick:hover {
  border-color: rgba(var(--primary-rgb), 0.4);
}

.ad-node-pick.is-on {
  border-color: transparent;
  color: #fff;
  background: linear-gradient(140deg, var(--primary-light), var(--primary));
  box-shadow: 0 8px 20px -8px rgba(var(--primary-rgb), 0.55);
}

.ad-node-pick.is-on i,
.ad-node-pick.is-on small {
  color: rgba(255, 255, 255, 0.85);
}

/* ── 多文件清单 ──────────────────────────────────────────── */
.ad-files {
  width: 100%;
  max-width: 460px;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  animation: adRise 0.34s var(--ease-out) backwards;
}

.ad-files__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text-muted);
  padding: 0 var(--sp-1);
}

.ad-files__list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  max-height: 240px;
  overflow-y: auto;
  padding-right: 2px;
}

/* 发送按钮常驻底部：选完文件后它是唯一的主操作，不该被长列表顶出屏幕外。
   sticky 让它贴在 .ad-body 可见区的底边 —— 滚动时始终可点，不滚时按正常流排布。
   背景本身是不透明渐变（.ad-btn--primary），不会有内容透视上来。 */
.ad-btn--send {
  position: sticky;
  bottom: 0;
  z-index: 3;
  margin-top: var(--sp-1);
  width: 100%;
  flex: none;
  /* 贴底时下方要留一点缝，否则会与 .ad-foot 的细字挤在一起 */
  margin-bottom: var(--sp-1);
}
