/* ============================================================
   通用浮层：toast / 确认框 / 输入框 / 登录页
   与 theme.css 的设计令牌配套，双端共用
   ============================================================ */

/* ---------- Toast ---------- */
.wb-toast{
  position:fixed;left:50%;bottom:calc(28px + env(safe-area-inset-bottom));
  transform:translate(-50%,14px);z-index:9000;
  display:flex;align-items:center;gap:8px;
  max-width:min(86vw,420px);padding:10px 15px;border-radius:var(--r-md);
  background:rgba(20,22,28,.94);color:var(--on-brand);
  font-size:var(--fz-title);line-height:var(--lh-snug);font-weight:var(--fw-m);
  box-shadow:0 14px 34px -10px rgba(17,24,39,.42);
  opacity:0;pointer-events:none;transition:opacity .2s ease,transform .22s cubic-bezier(.2,.9,.3,1);
  backdrop-filter:blur(10px);
}
.wb-toast.on{opacity:1;transform:translate(-50%,0)}
.wb-toast.ok{background:rgba(15,122,72,.96)}
.wb-toast.err{background:rgba(196,47,53,.96)}
.wb-toast .ti{width:15px;height:15px;flex:none;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}

/* ---------- 遮罩 + 对话框 ---------- */
.wb-mask{
  position:fixed;inset:0;z-index:8800;display:none;
  align-items:center;justify-content:center;padding:18px;
  background:rgba(15,17,22,.46);backdrop-filter:blur(3px);
}
.wb-mask.on{display:flex;animation:wbfade .18s ease}
@keyframes wbfade{from{opacity:0}to{opacity:1}}
.wb-dlg{
  width:420px;max-width:100%;max-height:calc(100vh - 40px);overflow:auto;
  background:var(--surface);border-radius:var(--r-lg);
  box-shadow:0 28px 64px -18px rgba(17,24,39,.34);
  animation:wbpop .2s cubic-bezier(.2,.9,.3,1);
}
@keyframes wbpop{from{opacity:0;transform:translateY(10px) scale(.985)}to{opacity:1;transform:none}}
/* 内容型弹窗（列表 / 正文 / 对照表）—— 与 420 的表单确认框并列的一档。
   类名取 wide 而不是 lg：.lg 在两端都是 KPI 图例的 flex 行，
   它的 align-items:flex-start 会把弹窗子项压成内容宽度（正文只占左侧一半）。
   尺寸（宽 / 高 / 滚动区）只在这里定义，JS 不再各自写内联。
   以前「跟发详情」是 880×62vh，「最新内容」还是默认 420×52vh —— 两个同构的
   分享清单窗口，一个宽一倍、一个窄到书名折行，看起来完全不是一套东西。 */
.wb-dlg.wide{
  width:min(880px, calc(100vw - 36px));
  /* 高度写死而不是跟着内容长：跟发详情（内容 370）和最新内容（内容 630+）
     以前一个 480 一个 760，开两个窗口像两个产品。现在统一 660，内容少的下方
     留白、内容多的正文区自己滚，头尾位置始终不动。 */
  height:min(660px, calc(100vh - 72px));
  display:flex;flex-direction:column;overflow:hidden;
}
/* 头尾固定、只有正文滚：内容再多也不会把页脚顶出屏幕 */
.wb-dlg.wide > .wb-dlg-hd{flex:none}
.wb-dlg.wide > .wb-dlg-bd{flex:1;min-height:0;overflow:auto;padding-bottom:10px}
.wb-dlg.wide > .wb-dlg-ft{flex:none}
@media (max-width:640px){
  /* 手机上不写死：354 宽的居中弹窗固定 660 高会和底部 sheet 打架，交给内容 */
  .wb-dlg.wide{height:auto;max-height:calc(100vh - 36px)}
}
.wb-dlg-hd{padding:18px 20px 0}
.wb-dlg-hd h3{font-size:var(--fz-h2);font-weight:var(--fw-sb);letter-spacing:var(--ls-normal)}
.wb-dlg-hd p{margin-top:5px;font-size:var(--fz-body);color:var(--text-3);line-height:var(--lh-base);white-space:pre-line}
/* 上面的 pre-line 是必须的：确认框的 message 里普遍用 \n 分段
   （「删除用户」的三行说明、删除账号的连带后果），没有它浏览器会把换行折成空格，
   几条后果糊成一整段，用户读不出重点。 */
.wb-dlg-bd{padding:14px 20px 4px}
.wb-dlg-bd .fld{margin-bottom:12px}
.wb-dlg-bd label{display:block;font-size:var(--fz-body);font-weight:var(--fw-sb);color:var(--text-2);margin-bottom:6px}
.wb-dlg-bd input,.wb-dlg-bd textarea{
  width:100%;padding:10px 12px;border:1px solid var(--line);border-radius:var(--r-sm);
  background:var(--surface);font-size:var(--fz-title);outline:none;transition:border-color .15s,box-shadow .15s;
}
.wb-dlg-bd textarea{min-height:96px;resize:vertical;font-family:var(--mono);font-size:var(--fz-body);line-height:var(--lh-base)}
.wb-dlg-bd input:focus,.wb-dlg-bd textarea:focus{border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-soft)}
.wb-dlg-bd input.bad,.wb-dlg-bd textarea.bad{border-color:var(--danger-deep);box-shadow:0 0 0 3px var(--danger-soft)}
.wb-dlg-ft{display:flex;justify-content:flex-end;gap:8px;padding:14px 20px 18px}
/* 弹窗按钮：与页面 .btn 完全同一套视觉 —— 34h / --r-sm / 500 字重 / 同一道橙渐变。
   以前弹窗主按钮是纯色 var(--brand) 且字重 600，比页面按钮更粗更亮，
   同一个「保存」在两处长得不一样。.qb-btn 原本是这份样式的完整副本，
   现合并到同一组选择器，高度也改取 --h 令牌，不再写裸 34。 */
.wb-btn,.qb-btn{
  display:inline-flex;align-items:center;justify-content:center;gap:6px;
  height:var(--h);padding:0 14px;border:1px solid var(--line);border-radius:var(--r-sm);
  background:var(--surface);color:var(--text);font-size:var(--fz-title);font-weight:var(--fw-m);
  line-height:1;cursor:pointer;
  transition:background .15s,border-color .15s,box-shadow .15s,transform .12s;
}
.wb-btn:hover,.qb-btn:hover{background:var(--surface-2);border-color:var(--line-strong)}
.wb-btn:active,.qb-btn:active{transform:translateY(1px)}
.wb-btn:focus-visible,.qb-btn:focus-visible{outline:none;border-color:var(--brand-focus);box-shadow:var(--ring)}
.wb-btn.primary,.qb-btn.primary{
  background:linear-gradient(180deg,var(--brand-mid),var(--brand-deep));
  border-color:var(--brand-ink);color:var(--on-brand);
  box-shadow:0 1px 2px rgba(228,85,31,.26),inset 0 1px 0 rgba(255,255,255,.2);
}
.wb-btn.primary:hover,.qb-btn.primary:hover{
  background:linear-gradient(180deg,var(--brand),var(--brand-deep));border-color:var(--brand-ink);
}
.wb-btn.danger{background:var(--danger);border-color:var(--danger-deep);color:var(--on-brand)}
.wb-btn.danger:hover{background:var(--danger-deep);border-color:var(--danger-deep)}
.wb-btn[disabled],.qb-btn[disabled]{opacity:.55;cursor:not-allowed;transform:none}

/* ============================================================
   文件管理：账号切换 + 加载 / 空态
   ============================================================ */
.fm-acc-box{display:flex;align-items:center;gap:7px;flex-wrap:wrap;justify-content:flex-end}
.fm-acc-dot{width:6px;height:6px;border-radius:50%;flex:none;display:block}
.fm-acc-tip{font-size:var(--fz-body);color:var(--text-3)}
.fm-acc-box select{
  height:var(--h-sm);padding:0 26px 0 10px;border:1px solid var(--line);border-radius:var(--r-sm);
  background:var(--surface);font-size:var(--fz-body);color:var(--text);cursor:pointer;
  appearance:none;-webkit-appearance:none;outline:none;
  background-image:linear-gradient(45deg,transparent 50%,var(--text-3) 50%),linear-gradient(135deg,var(--text-3) 50%,transparent 50%);
  background-position:calc(100% - 13px) 11px,calc(100% - 9px) 11px;
  background-size:4px 4px,4px 4px;background-repeat:no-repeat;
  transition:border-color .15s,box-shadow .15s;
}
.fm-acc-box select:focus{border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-soft)}
.fm-empty{
  justify-content:center;color:var(--text-3);font-size:var(--fz-body);
  padding:26px 16px;line-height:var(--lh-loose);text-align:center;
}
.fm-spin{
  width:13px;height:13px;border-radius:50%;flex:none;margin-right:8px;
  border:2px solid var(--line);border-top-color:var(--brand);
  animation:fmspin .7s linear infinite;display:inline-block;vertical-align:-2px;
}
@keyframes fmspin{to{transform:rotate(360deg)}}
.qb-mask{
  position:fixed;inset:0;z-index:8900;display:none;
  align-items:center;justify-content:center;padding:18px;
  background:rgba(15,17,22,.5);backdrop-filter:blur(3px);
}
.qb-mask.on{display:flex;animation:wbfade .18s ease}
.qb-dlg{
  width:430px;max-width:100%;max-height:calc(100vh - 36px);
  display:flex;flex-direction:column;
  background:var(--surface);border-radius:var(--r-lg);
  box-shadow:0 28px 64px -18px rgba(17,24,39,.34);
  animation:wbpop .2s cubic-bezier(.2,.9,.3,1);
}
.qb-dlg.wide{width:580px}
.qb-hd{display:flex;align-items:flex-start;gap:10px;padding:18px 18px 0}
.qb-hd h3{flex:1;font-size:var(--fz-h2);font-weight:var(--fw-sb);letter-spacing:var(--ls-normal);line-height:var(--lh-snug)}
.qb-x{
  width:26px;height:26px;flex:none;display:grid;place-items:center;
  border:0;border-radius:var(--r-sm);background:transparent;color:var(--text-3);cursor:pointer;
  transition:background .15s,color .15s;
}
.qb-x:hover{background:var(--surface-2);color:var(--text)}
.qb-sub{margin:6px 18px 0;font-size:var(--fz-body);color:var(--text-3);line-height:var(--lh-base)}
.qb-bd{padding:16px 18px 4px;overflow:auto;flex:1;min-height:0}
.qb-ft{display:flex;justify-content:flex-end;gap:8px;padding:12px 18px 18px;flex-wrap:wrap}
/* .qb-btn 的样式已合并到上方 .wb-btn 组，这里不再重复定义 */

/* 二维码区 */
.qb-qr{
  width:198px;height:198px;margin:0 auto;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:2px;
  border:1px solid var(--line);border-radius:var(--r-md);background:var(--surface-2);
  color:var(--text-3);font-size:var(--fz-body);line-height:var(--lh-base);text-align:center;overflow:hidden;
}
.qb-qr img{width:100%;height:100%;object-fit:contain;background:var(--surface)}
.qb-qr.ok{
  border-color:var(--success-line);background:var(--success-soft);
  color:var(--success-deep);font-size:var(--fz-h2);font-weight:var(--fw-sb);
}
.qb-qr.err{
  border-color:var(--danger-line);background:var(--danger-soft);color:var(--danger-deep);font-size:var(--fz-body);
}
.qb-stat{margin-top:13px;text-align:center;font-size:var(--fz-body);color:var(--text-2);line-height:var(--lh-base);min-height:20px}
.qb-stat b{color:var(--text);font-weight:var(--fw-sb)}
.qb-stat.ok{color:var(--success-deep)}
.qb-stat.err{color:var(--danger-deep)}

/* 滑块容器 */
.qb-cap{min-height:180px;display:grid;place-items:center;font-size:var(--fz-body);color:var(--text-3)}

/* 目录选择器 */
.qb-crumb{
  display:flex;align-items:center;gap:3px;flex-wrap:wrap;
  font-size:var(--fz-body);color:var(--text-3);margin-bottom:9px;min-height:20px;
}
.qb-crumb span{color:var(--line);}
.qb-crumb button{
  border:0;background:transparent;color:var(--info-deep);cursor:pointer;
  font-size:var(--fz-body);padding:2px 3px;border-radius:var(--r-xs);font-family:inherit;
}
.qb-crumb button:hover{background:var(--brand-soft)}
.qb-list{
  max-height:min(48vh,340px);overflow:auto;
  border:1px solid var(--line);border-radius:var(--r-md);background:var(--surface);
}
.qb-row{
  display:flex;align-items:center;gap:9px;padding:9px 11px;
  border-bottom:1px solid var(--line-soft);cursor:pointer;font-size:var(--fz-title);
  transition:background .12s;
}
.qb-row:last-child{border-bottom:0}
.qb-row:hover{background:var(--surface-2)}
.qb-row.sel{background:var(--brand-soft)}
.qb-row .ic{display:grid;place-items:center;width:26px;height:26px;flex:none;border-radius:var(--r-sm);background:var(--warn-soft);color:var(--warn-deep)}
.qb-row .nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.qb-row .go{
  border:0;background:transparent;color:var(--text-3);font-size:var(--fz-meta);
  cursor:pointer;padding:3px 5px;border-radius:var(--r-xs);font-family:inherit;
}
.qb-row .go:hover{background:var(--surface);color:var(--brand)}
.qb-empty{padding:26px 14px;text-align:center;font-size:var(--fz-body);color:var(--text-3);line-height:var(--lh-loose)}

@media (max-width:640px){
  .qb-mask{padding:12px;align-items:flex-end}
  .qb-dlg{width:100%;max-height:calc(100vh - 24px);border-radius:var(--r-lg)}
  .qb-qr{width:176px;height:176px}
  .qb-list{max-height:44vh}
  .qb-ft{padding:10px 14px 16px}
  .qb-bd{padding:14px 14px 4px}
  .qb-hd{padding:16px 14px 0}
  .qb-sub{margin:6px 14px 0}
}

/* ============================================================
   正文排版：一条微博里的「标题 + 链接」分享清单
   ------------------------------------------------------------
   「最新内容」弹窗和跟发详情里的「改写前 / 改写后」共用这一套。
   以前详情那边是 white-space:pre-wrap 铺纯文本，书名和地址同字号同颜色，
   一整块读下来分不清哪里断句；现在两边都走 CORE.contentHtml / postBodyHtml。
   字号沿用两端各自的 --fz-* 阶梯，所以在桌面和手机上都跟着各自的正文走。
   ============================================================ */
.post-body{font-size:var(--fz-body);line-height:var(--lh-base);color:var(--text-2)}
/* 非分享型正文：整段保留换行 */
.post-plain{font-size:var(--fz-title);line-height:var(--lh-base);color:var(--text);white-space:pre-wrap;word-break:break-word}
/* 【电子书分享】这类整组抬头 */
.post-intro{
  font-size:var(--fz-title);line-height:var(--lh-base);color:var(--text);font-weight:var(--fw-m);
  white-space:pre-wrap;word-break:break-word;margin-bottom:9px;
}
.post-item{margin-bottom:9px}
.post-item:last-child{margin-bottom:0}
.post-title{font-size:var(--fz-title);line-height:var(--lh-snug);font-weight:var(--fw-m);color:var(--text);word-break:break-word}
.post-link{font-size:var(--fz-body);line-height:var(--lh-base);color:var(--text-2);margin-top:2px;word-break:break-all}
.post-link a,.post-plain a{color:var(--info-deep);text-decoration:none}
.post-link a:hover,.post-plain a:hover{text-decoration:underline}
.post-url{color:var(--text-3)}
/* 「网盘」标与提取码：贴着地址尾巴，不参与换行断字 */
.post-link .post-disk,
.post-link .post-code{
  display:inline-flex;align-items:center;margin-left:5px;font-size:var(--fz-cap);
  padding:1px 6px;vertical-align:1px;white-space:nowrap;
}

/* ============================================================
   内容列表弹窗（「最新内容」「跟发详情」这类逐条正文的窗口）
   ------------------------------------------------------------
   原来每条只是一根 border-top 隔开的一坨：17 条微博里每条又带 9-10 个
   网盘链接，全展开后一屏只看得见半条，滚 9860px 才能到底，也分不清
   「哪一条是新的、哪一条还没跟发」。

   现在一条一张卡片：顶行是「时间 · 打开原帖 · 跟发这条」，正文默认只露
   约 5 行（渐变遮罩 + 展开），链接默认只留前 2 条（其余折叠）。
   ============================================================ */
.tg-list{display:flex;flex-direction:column;gap:10px;padding:2px 0 8px}
.tg-card{
  border:1px solid var(--line-soft);border-radius:var(--r-md);background:var(--surface);
  padding:12px 14px 10px;
  transition:border-color .18s,box-shadow .18s;
  animation:tgIn .3s cubic-bezier(.2,.9,.3,1) both;
}
@keyframes tgIn{from{opacity:0;transform:translateY(7px)}to{opacity:1;transform:none}}
.tg-card:hover{border-color:var(--line-strong);box-shadow:0 8px 22px -16px rgba(17,24,39,.32)}
/* 已跟发的整卡降噪：用户扫一眼就知道哪条不用再管 */
.tg-card.is-done{background:var(--surface-2);border-color:transparent}
.tg-card.is-done .tg-card-bd{color:var(--text-3)}

.tg-card-hd{display:flex;align-items:center;gap:8px;min-height:28px;margin-bottom:6px}
.tg-when{font-size:var(--fz-meta);color:var(--text-3);font-variant-numeric:tabular-nums;flex:none}
.tg-dot{width:3px;height:3px;border-radius:50%;background:var(--line);flex:none;display:block}
.tg-open{
  font-size:var(--fz-meta);color:var(--info-deep);text-decoration:none;
  display:inline-flex;align-items:center;gap:3px;flex:none;
}
.tg-open:hover{text-decoration:underline}
.tg-open svg{width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:1.9}
/* 跟发按钮：与页面 .btn-sm 同档（28h / 8px），不再自造 24h */
.tg-follow{
  margin-left:auto;height:28px;padding:0 11px;border-radius:var(--r-sm);
  font-size:var(--fz-body);font-weight:var(--fw-m);
  display:inline-flex;align-items:center;gap:5px;
}
.tg-follow svg{width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2}
.tg-follow.ok{background:var(--success-soft);border-color:var(--success-line);color:var(--success-deep)}
.tg-done{
  margin-left:auto;display:inline-flex;align-items:center;gap:4px;
  font-size:var(--fz-meta);padding:2px 8px;
}
.tg-done svg{width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:2.4}

/* 折叠正文：容器限高 + 底部渐变，展开后自然松开 */
.tg-card-bd{position:relative;max-height:132px;overflow:hidden;transition:max-height .32s cubic-bezier(.3,.9,.3,1)}
.tg-card.no-clamp .tg-card-bd{max-height:none}
.tg-card.expanded .tg-card-bd{max-height:none}
.tg-card-bd::after{
  content:'';position:absolute;left:0;right:0;bottom:0;height:46px;pointer-events:none;
  background:linear-gradient(180deg,rgba(255,255,255,0),var(--surface));
  transition:opacity .2s;
}
/* is-done 卡片的遮罩要跟它自己的底色（--surface-2 var(--surface-2)）对齐，
   否则已跟发的卡片底部会出现一条比卡片更白/更灰的横带 */
.tg-card.is-done .tg-card-bd::after{background:linear-gradient(180deg,rgba(249,250,251,0),var(--surface-2))}
.tg-card.expanded .tg-card-bd::after,
.tg-card.no-clamp .tg-card-bd::after{opacity:0}
/* 链接只留前两条：第三个 .post-item 起整组收掉（兄弟选择器，不吃 intro 的序号） */
.tg-card:not(.expanded) .post-body .post-item ~ .post-item ~ .post-item{display:none}

.tg-more{
  display:none;margin-top:5px;padding:5px 0;min-height:26px;box-sizing:border-box;
  border:0;background:none;cursor:pointer;
  font-size:var(--fz-body);font-weight:var(--fw-m);color:var(--brand-ink);
  align-items:center;gap:3px;
}
.tg-more svg{width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2.2;transition:transform .22s}
.tg-more:hover{text-decoration:underline}
.tg-card.clamped .tg-more{display:inline-flex}
.tg-card.expanded .tg-more svg{transform:rotate(180deg)}

/* 抓取进行中：列表压暗且不可点（此刻的列表马上就要被重绘替换） */
.wb-dlg-bd.is-loading{opacity:.5;pointer-events:none;transition:opacity .2s}
.wb-dlg-bd{transition:opacity .2s}

/* 空状态 */
.tg-empty{
  padding:52px 20px;display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center;
}
.tg-empty svg{width:34px;height:34px;stroke:var(--text-3);fill:none;stroke-width:1.5;opacity:.5;margin-bottom:2px}
.tg-empty b{font-size:var(--fz-title);font-weight:var(--fw-sb);color:var(--text-2)}
.tg-empty span{font-size:var(--fz-body);color:var(--text-3);line-height:var(--lh-base);max-width:300px}

/* ---------------- 立即抓取：三态 + 进度条 ---------------- */
.tg-progress{
  height:2px;margin:0 20px;border-radius:var(--r-xs);background:transparent;overflow:hidden;
  opacity:0;transition:opacity .2s;flex:none;
}
.tg-progress.on{opacity:1;background:var(--line-soft)}
.tg-progress::after{
  content:'';display:block;height:100%;width:38%;border-radius:var(--r-xs);
  background:linear-gradient(90deg,transparent,var(--brand),transparent);
  animation:tgSlide 1.15s ease-in-out infinite;
}
@keyframes tgSlide{0%{transform:translateX(-110%)}100%{transform:translateX(310%)}}

.tg-fetch{
  min-width:104px;justify-content:center;gap:6px;
  transition:background .22s,color .22s,border-color .22s,box-shadow .22s;
}
.tg-fetch svg{width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:2;flex:none}
.tg-fetch .tg-spin{
  width:13px;height:13px;border-radius:50%;flex:none;display:none;
  border:2px solid rgba(255,255,255,.4);border-top-color:var(--on-brand);
  animation:tgSpin .68s linear infinite;
}
@keyframes tgSpin{to{transform:rotate(360deg)}}
.tg-fetch.busy .tg-spin{display:block}
.tg-fetch.busy .tg-ico-fetch{display:none}
.tg-fetch .tg-ico-ok{display:none}
/* 成功：整颗按钮翻成绿色并轻微抬起，2 秒后自己退回 */
.tg-fetch.ok{
  background:var(--success);border-color:var(--success-deep);color:var(--on-brand);
  box-shadow:0 6px 16px -8px rgba(22,163,74,.6);
}
.tg-fetch.ok .tg-ico-fetch{display:none}
.tg-fetch.ok .tg-ico-ok{display:block}
.tg-fetch.ok:active{transform:none}
/* 失败：抖一下并转红，明确「这是错误不是没反应」 */
.tg-fetch.err{
  background:var(--danger);border-color:var(--danger-deep);color:var(--on-brand);
  animation:tgShake .46s cubic-bezier(.36,.07,.19,.97);
}
.tg-fetch.err .tg-ico-fetch{display:none}
@keyframes tgShake{
  10%,90%{transform:translateX(-1px)}
  20%,80%{transform:translateX(2px)}
  30%,50%,70%{transform:translateX(-3px)}
  40%,60%{transform:translateX(3px)}
}
/* 页脚左侧统计与说明，让「共几条 / 还差几条」不用回滚到顶部才看得见 */
.tg-stat{
  margin-right:auto;display:flex;align-items:center;gap:7px;
  font-size:var(--fz-meta);color:var(--text-3);
}
.tg-stat b{color:var(--text-2);font-weight:var(--fw-m);font-variant-numeric:tabular-nums}
.tg-stat i{width:1px;height:11px;background:var(--line);display:block}

/* 尊重系统「减少动态效果」：动画只留信息量（三态色块），去掉位移与旋转 */
@media (prefers-reduced-motion: reduce){
  .tg-card{animation:none}
  .tg-card-bd{transition:none}
  .tg-progress::after{animation:none;width:100%;background:var(--brand-soft)}
  .tg-fetch .tg-spin{animation:none;border-top-color:rgba(255,255,255,.9)}
  .tg-fetch.err{animation:none}
}

/* ============================================================
   骨架屏（首屏数据未到时的占位）
   ------------------------------------------------------------
   模板 HTML 里留着一整套演示数据（「演示主账号」「影视资源速递」
   「demo_admin」…）。登录后真实数据要等一两个请求才回来，这中间
   屏幕上显示的是别人的测试数据 —— 用户会以为那就是自己的账号，
   甚至照着它去点「删除」。
   现在初始化第一件事就是把这几块清成骨架，真实渲染接管后自然覆盖。
   ============================================================ */
.sk-bar{
  display:block;height:9px;border-radius:var(--r-xs);
  background:linear-gradient(90deg,var(--surface-2) 0%,var(--fill) 50%,var(--surface-2) 100%);
  background-size:220% 100%;
  animation:skShimmer 1.25s linear infinite;
}
@keyframes skShimmer{0%{background-position:110% 0}100%{background-position:-110% 0}}
.sk-bar.w40{width:40%}
.sk-bar.w55{width:55%}
.sk-bar.w70{width:70%}
.sk-bar.w85{width:85%}
.sk-bar + .sk-bar{margin-top:6px}
.sk-row > td{padding:13px 10px;border-bottom:1px solid var(--line-soft)}
.sk-row:last-child > td{border-bottom:0}
/* 骨架卡（账号卡 / 内容卡） */
.sk-card{
  border:1px solid var(--line-soft);border-radius:var(--r-md);background:var(--surface);
  padding:14px 15px;display:flex;flex-direction:column;gap:9px;
}
.sk-card .sk-ava{width:34px;height:34px;border-radius:50%;flex:none;
  background:linear-gradient(90deg,var(--surface-2) 0%,var(--fill) 50%,var(--surface-2) 100%);
  background-size:220% 100%;animation:skShimmer 1.25s linear infinite}
.sk-card-hd{display:flex;align-items:center;gap:9px}
.sk-card-hd .sk-bar{flex:1;margin:0}
@media (prefers-reduced-motion: reduce){
  .sk-bar,.sk-card .sk-ava{animation:none;background:var(--surface-2)}
}

/* ============================================================
   网盘预设：字段锁定 + 分区卡（双端共用）
   ------------------------------------------------------------
   「锁定」= 这个值由超管统一规定，用户改不了。它是一个**有语义的状态**，
   必须一眼看得出、一点就变。

   改造前它是 13px 原生 checkbox，实测四个问题：
     ① 点击区 39×15，低于 28 的最小触摸目标；
     ② 勾选后 label 颜色不变（仍 rgb(101,110,126)）、无底色变化 —— 零反馈；
     ③ 「已锁定 N 项」不跟着动（只在整页重渲染时才更新），
        于是超管会以为没点上、反复点；
     ④ 11 个锁定项里「锁定保存目录」那个是 846×16，横跨整行，
        与其余 39×15 完全不是一套东西。

   现在它是一个药丸按钮，两种状态各有完整的视觉（图标 + 文字 + 底色 + 描边）：
     未锁定  🔓「锁定」   白底描边，灰字
     已锁定  🔒「已锁定」 品牌浅底 + 深橙字 + 品牌描边 + 字段左侧竖条
   状态只由 aria-pressed 一个属性承载，文字与图标由 CSS 切换 ——
   JS 不碰文案，回显与交互走同一条路径，不会出现「点了变、回显变回去」。
   ============================================================ */

/* 共享组件在两端各取自己阶梯里「同角色」的那一档。
   默认给的是移动端档位；桌面端在 desktop.html 里覆盖成 --fz-d-*（见那里）。 */
.cp-sum,.cp-sec,.cp-field{
  --cpf-cap:var(--fz-cap);
  --cpf-body:var(--fz-body);
  --cpf-title:var(--fz-title);
  --cpf-head:var(--fz-h2);
  --cpf-num:var(--fz-h2);
}

/* ---- 总览条：它是摘要，不是卡片，所以沉到二级面、不加投影 ---- */
.cp-sum{
  display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  padding:12px 15px;margin-bottom:14px;
  background:var(--surface-2);border:1px solid var(--line-soft);border-radius:var(--r-md);
}
.cp-sum-l{display:flex;align-items:center;gap:10px;min-width:0}
.cp-sum-ico{
  width:var(--h-sm);height:var(--h-sm);border-radius:var(--r-sm);flex:none;
  display:grid;place-items:center;
  background:var(--surface);border:1px solid var(--line);color:var(--text-3);
  transition:color .18s,border-color .18s,background .18s;
}
.cp-sum-ico svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
/* 有锁定项时，总览图标点亮成品牌色 —— 这是「全局有锁」的唯一提醒 */
.cp-sum.has .cp-sum-ico{background:var(--brand-soft);border-color:var(--brand-line);color:var(--brand-ink)}
.cp-sum-n{font-size:var(--cpf-title);color:var(--text-2);line-height:1.3;white-space:nowrap}
.cp-sum-n b{font-size:var(--cpf-num);font-weight:var(--fw-b);color:var(--text);font-variant-numeric:tabular-nums;letter-spacing:var(--ls-tight)}
.cp-sum.has .cp-sum-n b{color:var(--brand-ink)}
.cp-sum-n i{font-style:normal;color:var(--text-3);font-size:var(--cpf-body)}
/* 进度条：3px，锁了几项一眼可量 */
.cp-sum-bar{height:3px;width:112px;margin-top:6px;border-radius:2px;background:var(--line);overflow:hidden}
.cp-sum-bar i{
  display:block;height:100%;width:0;border-radius:2px;
  background:linear-gradient(90deg,var(--brand-mid),var(--brand-deep));
  transition:width .3s cubic-bezier(.3,.9,.3,1);
}
.cp-sum-meta{font-size:var(--cpf-body);color:var(--text-3);min-width:0;line-height:var(--lh-snug)}
.cp-sum-meta b{color:var(--text-2);font-weight:var(--fw-m)}
.cp-sum-act{margin-left:auto;display:flex;align-items:center;gap:6px;flex:none}

/* ---- 分区卡：白卡 + 投影，与总览条拉开一层 ---- */
.cp-sec{
  border:1px solid var(--line);border-radius:var(--r-lg);background:var(--surface);
  box-shadow:var(--sh-1);margin-bottom:12px;overflow:hidden;
}
.cp-sec-hd{
  display:flex;align-items:center;gap:9px;padding:11px 15px;
  background:linear-gradient(180deg,#FFFFFF,#FBFCFD);
  border-bottom:1px solid var(--line-soft);
}
/* 分区标记：一根 3px 竖条，和「已锁定」字段用的竖条同一种形状，
   但颜色是中性灰 —— 同一个形状两种含义（这里是分区、那里是锁态），
   所以只在锁态才染色，分区态保持无色 */
.cp-sec-hd::before{content:"";width:3px;height:13px;border-radius:2px;background:var(--line-strong);flex:none}
.cp-sec-t{font-size:var(--cpf-head);font-weight:var(--fw-sb);color:var(--text);letter-spacing:var(--ls-normal);line-height:var(--lh-snug)}
.cp-sec-sub{font-size:var(--cpf-body);color:var(--text-3);font-weight:var(--fw-r);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 分区锁定计数：0 项时保持中性灰，有锁才点橙 —— 不然每个分区头都在喊 */
.cp-sec-cnt{
  margin-left:auto;flex:none;font-size:var(--cpf-body);font-variant-numeric:tabular-nums;
  color:var(--text-3);padding:1px 7px;border-radius:var(--r-pill);
  background:var(--fill);transition:background .18s,color .18s;
}
.cp-sec-cnt.has{background:var(--brand-soft);color:var(--brand-ink);font-weight:var(--fw-sb)}

/* ---- 字段行 ---- */
.cp-field{
  padding:13px 15px;border-bottom:1px solid var(--line-soft);position:relative;
  transition:background .18s;
}
.cp-field:last-child{border-bottom:0}
/* 锁态的视觉锚点：左侧一道品牌竖条。用 ::before 而不是 border-left，
   这样不会让锁定字段的内容整体右移 3px（一旦右移，锁定/未锁定的输入框
   左边缘就不在一条线上了，整列会参差） */
.cp-field::before{
  content:"";position:absolute;left:0;top:11px;bottom:11px;width:3px;border-radius:0 3px 3px 0;
  background:var(--brand);opacity:0;transform:scaleY(.4);
  transition:opacity .2s,transform .2s cubic-bezier(.3,.9,.3,1);
}
.cp-field.locked::before{opacity:1;transform:scaleY(1)}
.cp-field.locked{background:linear-gradient(90deg,rgba(255,107,53,.045),rgba(255,107,53,0) 44%)}

.cp-fhd{display:flex;align-items:center;gap:10px;margin-bottom:7px;min-height:var(--h-sm)}
.cp-fk{flex:1;min-width:0;display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.cp-fname{font-size:var(--cpf-body);font-weight:var(--fw-m);color:var(--text-2);line-height:var(--lh-snug)}
.cp-fem{font-style:normal;font-size:var(--cpf-cap);color:var(--text-3);background:var(--fill);padding:1px 6px;border-radius:var(--r-xs)}
/* 锁态下字段名加深：说明「这一项现在有约束」 */
.cp-field.locked .cp-fname{color:var(--text);font-weight:var(--fw-sb)}

/* ---- 锁定按钮（核心交互）----
   定宽 76：两种状态的文案长度不同（「锁定」3 字 / 「已锁定」3 字但字重更粗），
   不定宽的话切换瞬间按钮左边缘会跳 12px，一屏 11 个按钮连着点就是一片抖动。 */
.cp-lock{
  flex:none;display:inline-flex;align-items:center;justify-content:center;gap:5px;
  height:var(--h-sm);min-width:76px;padding:0 10px;border-radius:var(--r-sm);
  border:1px solid var(--line);background:var(--surface);
  color:var(--text-3);font-size:var(--cpf-body);font-weight:var(--fw-m);
  font-family:inherit;line-height:1;white-space:nowrap;cursor:pointer;
  transition:background .16s,border-color .16s,color .16s,box-shadow .16s,transform .1s;
}
.cp-lock svg{width:13px;height:13px;flex:none;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.cp-lock:hover{border-color:var(--brand-line);color:var(--brand-ink);background:var(--brand-soft)}
.cp-lock:active{transform:translateY(.5px)}
.cp-lock:focus-visible{outline:none;border-color:var(--brand-focus);box-shadow:var(--ring)}
/* 已锁定：品牌浅底 + 深橙字 + 品牌描边，是「按下去了」的凸起感 */
.cp-lock[aria-pressed="true"]{
  background:var(--brand-soft);border-color:var(--brand-line);color:var(--brand-ink);
  font-weight:var(--fw-sb);
  box-shadow:inset 0 0 0 1px rgba(255,107,53,.1);
}
.cp-lock[aria-pressed="true"]:hover{background:var(--tint-brand-bg);border-color:var(--brand)}
/* 图标与文案的显隐：只认 aria-pressed，JS 不改文案 */
.cp-lock .cp-on{display:none}
.cp-lock[aria-pressed="true"] .cp-on{display:inline}
.cp-lock[aria-pressed="true"] .cp-off{display:none}

/* ---- 字段体与说明 ---- */
.cp-fbd{display:grid;gap:8px}
/* 字段说明用正文档（12），不用角标档（10）：
   这里的句子是「服务端会请求该地址判断分享是否失效，留空则跳过检测，
   禁止填内网地址」——10px 的说明文字在 1440 屏上要靠凑近才读得清，
   而它承担的是「填错会出事」的告知责任，不能比正文更容易被跳过。 */
.cp-fhint{margin:7px 0 0;font-size:var(--cpf-body);color:var(--text-3);line-height:var(--lh-base)}
.cp-fhint code{font-family:var(--mono);font-size:var(--cpf-body);background:var(--fill);border-radius:var(--r-xs);padding:1px 5px}

/* 保存目录：四盘两列，窄屏一列 */
.cp-paths{display:grid;grid-template-columns:1fr 1fr;gap:9px}
.cp-path{display:grid;gap:4px;min-width:0}
.cp-path>span{font-size:var(--cpf-body);color:var(--text-3);font-weight:var(--fw-m);line-height:var(--lh-snug)}

/* 开关行：开关与说明并排 */
.cp-switchrow{display:flex;align-items:center;gap:11px;flex-wrap:wrap}
.cp-switchrow .cp-switch-tx{font-size:var(--cpf-cap);color:var(--text-3);line-height:var(--lh-snug);flex:1;min-width:120px}

@media (max-width:560px){
  .cp-sum{gap:10px;padding:11px 13px}
  .cp-sum-act{width:100%;margin-left:0}
  .cp-sum-act .btn{flex:1}
  .cp-paths{grid-template-columns:1fr}
  .cp-field{padding:12px 13px}
  .cp-sec-hd{padding:10px 13px}
  .cp-sec-sub{display:none}
}

/* 「锁定项有改动，还没保存」的提示：锁态是「改完还要保存」的两步操作，
   不提示的话超管点完看到满屏橙色会以为已经生效，刷新后又回到原样。 */
.set-tip.is-dirty{color:var(--warn-deep);font-weight:var(--fw-m)}
.set-tip.is-dirty::before{
  content:"";display:inline-block;width:5px;height:5px;border-radius:50%;
  background:var(--warn);margin-right:6px;vertical-align:1px;
  animation:cpDot 1.5s ease-in-out infinite;
}
@keyframes cpDot{0%,100%{opacity:1}50%{opacity:.35}}
@media (prefers-reduced-motion:reduce){.set-tip.is-dirty::before{animation:none}}
