/* ==========================================================================
   丙大山古玩市场 · 交互组件库
   14 个组件，每个对应一种可验证的微交互契约。
   编号与需求一一对应，见文件头注释。
   ========================================================================== */

/* --------------------------------------------------------------------------
   ① 多选筛选标签 MultiSelectTags
   契约：选中时左侧长出对勾 → 底色填满 → 宽度平滑撑开
        相邻标签同步让位（flex 布局自动处理）
        筛选按钮上的数量跳动更新
   -------------------------------------------------------------------------- */
.mst-bar { display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: center; }

.mst-tag {
  --pad: 12px;
  position: relative;
  display: inline-flex;
  align-items: center;
  height: 34px;
  padding: 0 var(--s-4);
  border-radius: var(--r-full);
  background: var(--surface);
  color: var(--text-2);
  font-size: 13.5px;
  white-space: nowrap;
  box-shadow: var(--neu-sm);
  /* width 由 JS 测量后写入 --w，transition 让撑开过程平滑 */
  transition: box-shadow var(--t-mid) var(--ease),
              background var(--t-mid) var(--ease),
              color var(--t-mid) var(--ease);
  overflow: hidden;
}
.mst-tag:hover { color: var(--blue-600); }

/* 对勾：从左侧长出。用 width+opacity，不触发重排抖动 */
.mst-check {
  width: 0;
  opacity: 0;
  margin-right: 0;
  transition: width var(--t-mid) var(--ease-out),
              opacity var(--t-fast) var(--ease),
              margin-right var(--t-mid) var(--ease);
}
.mst-tag[aria-pressed="true"] {
  background: var(--blue-500);
  color: var(--text-on);
  box-shadow: 3px 3px 8px var(--shadow-dark), -2px -2px 7px var(--shadow-light);
}
.mst-tag[aria-pressed="true"] .mst-check {
  width: 14px;
  opacity: 1;
  margin-right: 6px;
}

/* 数量徽标：跳动更新（scale 弹一下） */
.mst-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 6px;
  border-radius: var(--r-full);
  background: var(--blue-100); color: var(--blue-700);
  font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums;
  transition: background var(--t-fast) var(--ease);
}
.mst-count.is-bump { animation: mst-bump 420ms var(--ease-back); }
@keyframes mst-bump {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.42); }
  100% { transform: scale(1); }
}

/* --------------------------------------------------------------------------
   ② 可删除标签 TagField
   契约：点叉 → 标签缩成圆点后消失 → 后续标签依次平滑补位
        输入框高度随之收缩
   -------------------------------------------------------------------------- */
.tagfield {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2);
  min-height: 44px; padding: 8px 10px;
  background: var(--surface-sunk);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  box-shadow: var(--neu-inset);
  cursor: text;
  /* 高度随内容变化，transition 让收缩平滑 */
  transition: border-color var(--t-fast) var(--ease),
              padding var(--t-mid) var(--ease);
}
.tagfield:focus-within { border-color: var(--blue-300); }

.tag {
  display: inline-flex; align-items: center; gap: 5px;
  height: 28px; padding: 0 6px 0 11px;
  border-radius: var(--r-full);
  background: var(--blue-100); color: var(--blue-700);
  font-size: 13px; white-space: nowrap;
  transition: background var(--t-fast) var(--ease),
              transform var(--t-mid) var(--ease-back),
              opacity var(--t-mid) var(--ease),
              max-width var(--t-mid) var(--ease),
              padding var(--t-mid) var(--ease),
              margin var(--t-mid) var(--ease);
}
.tag__x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: var(--r-full);
  color: var(--blue-500);
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.tag__x:hover { background: var(--blue-200); color: var(--blue-700); }

/* 删除动画：先横向收窄+内缩成圆点，再淡出移除 */
.tag.is-removing {
  max-width: 0;
  padding-left: 0; padding-right: 0;
  margin-right: -8px;
  opacity: 0;
  transform: scale(.35);
  pointer-events: none;
}
.tagfield__input {
  flex: 1; min-width: 90px;
  border: 0; background: none; outline: none;
  height: 28px; font-size: 14px;
}
.tagfield__input::placeholder { color: var(--text-3); }

/* --------------------------------------------------------------------------
   ③ 输入联想浮层 AutocompletePopover
   契约：输入时在光标旁弹出 → 实时筛选 → 选中以标签插入 → 浮层收起
   -------------------------------------------------------------------------- */
.ac { position: relative; }
.ac__panel {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px);
  max-height: 232px; overflow-y: auto;
  background: var(--surface);
  border-radius: var(--r-md);
  box-shadow: 0 10px 30px var(--shadow-dark-s), 0 2px 6px var(--shadow-dark-s);
  padding: 6px;
  z-index: var(--z-pop);
  opacity: 0; transform: translateY(-6px) scale(.98);
  pointer-events: none;
  transition: opacity var(--t-fast) var(--ease),
              transform var(--t-mid) var(--ease-out);
}
.ac__panel.is-open { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
.ac__item {
  display: flex; align-items: center; gap: var(--s-2);
  width: 100%; padding: 9px 10px;
  border-radius: var(--r-sm);
  font-size: 14px; text-align: left; color: var(--text);
  transition: background var(--t-fast) var(--ease);
}
.ac__item:hover,
.ac__item.is-active { background: var(--blue-50); color: var(--blue-700); }
.ac__item em { font-style: normal; color: var(--blue-500); font-weight: 600; }
.ac__group { padding: 6px 10px 3px; font-size: 11px; color: var(--text-3); letter-spacing: .5px; }
.ac__empty { padding: 14px 10px; font-size: 13px; color: var(--text-3); text-align: center; }

/* --------------------------------------------------------------------------
   ④ 字号刻度尺 FontScale
   契约：横向刻度 · 当前值居中高亮 · 两侧渐隐
        左右拨动实时变 · 停下吸附最近刻度
   -------------------------------------------------------------------------- */
.fscale {
  position: relative;
  height: 62px;
  padding: 6px var(--s-4) 0;
  background: var(--surface);
  border-top: 1px solid var(--grey-200);
  overflow: hidden;
  touch-action: pan-y;
  cursor: ew-resize;
  user-select: none;
}
.fscale__track {
  position: relative; height: 100%;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 18%, #000 82%, transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 18%, #000 82%, transparent 100%);
}
.fscale__tick {
  position: absolute; top: 50%; transform: translate(-50%, -50%);
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  transition: color var(--t-fast) var(--ease);
  color: var(--text-3);
}
.fscale__tick i { display: block; width: 2px; height: 9px; border-radius: 1px;
                  background: currentColor; transition: height var(--t-fast) var(--ease); }
.fscale__tick span { font-size: 10px; font-variant-numeric: tabular-nums; opacity: .75; }
.fscale__tick.is-current { color: var(--blue-500); }
.fscale__tick.is-current i { height: 18px; width: 3px; }
.fscale__tick.is-current span { opacity: 1; font-weight: 600; }
/* 吸附中：轻微回弹，给"咔哒"一下的手感 */
.fscale__tick.is-snapping { animation: fs-snap 260ms var(--ease-back); }
@keyframes fs-snap { 0% { transform: translate(-50%,-50%) scale(1.28); } 100% { transform: translate(-50%,-50%) scale(1); } }
.fscale__value {
  position: absolute; top: 4px; left: 50%; transform: translateX(-50%);
  font-size: 11px; color: var(--blue-500); font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   ⑤ 按压倾斜卡片 TiltCard
   契约：按下朝触点方向轻微倾斜 + 缩小一点
        背后光斑向触点聚拢 · 松手弹性复原
   -------------------------------------------------------------------------- */
.tilt {
  position: relative;
  transform-style: preserve-3d;
  will-change: transform;
  transition: transform var(--t-slow) var(--ease-back);
  isolation: isolate;
}
.tilt__glow {
  position: absolute; inset: -20%;
  border-radius: 50%;
  background: radial-gradient(circle at var(--gx,50%) var(--gy,50%),
              rgba(77,127,174,.20) 0%, rgba(77,127,174,0) 62%);
  opacity: 0;
  transform: scale(.55);
  transition: opacity var(--t-mid) var(--ease), transform var(--t-mid) var(--ease);
  pointer-events: none; z-index: -1;
}
.tilt.is-pressed { transition: transform var(--t-fast) var(--ease); }
.tilt.is-pressed .tilt__glow { opacity: 1; transform: scale(1); }

/* --------------------------------------------------------------------------
   ⑥ 长按变录音条 RecordBar
   契约：长按麦克风 → 按钮组横向拉长成录音条 + 实时波形
        加号旋转成取消 · 松手复原
   -------------------------------------------------------------------------- */
.recbar {
  position: relative;
  display: flex; align-items: center; gap: var(--s-2);
  height: 52px; padding: 0 var(--s-2);
  background: var(--surface);
  border-radius: var(--r-full);
  box-shadow: var(--neu);
  overflow: hidden;
  /* 契约：长按后"按钮组横向拉长成录音条"。
     所以静止态是收窄的（只容得下两个按钮），录音态才撑满。
     用 max-width 而不是 width —— width:auto↔100% 不可过渡，
     max-width 是可过渡的，动画才跑得起来。 */
  max-width: 120px;
  transition: max-width var(--t-mid) var(--ease-out);
}
.recbar.is-rec { max-width: 100%; }
.recbar__btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; flex: none;
  border-radius: var(--r-full);
  color: var(--text-2);
  transition: transform var(--t-mid) var(--ease-out),
              background var(--t-fast) var(--ease),
              color var(--t-fast) var(--ease);
}
.recbar__btn:hover { background: var(--grey-100); color: var(--blue-600); }
/* 加号 → 叉号：旋转 90° + 线条不硬切（用两条线各自变形） */
.recbar.is-rec .recbar__plus { transform: rotate(135deg); color: var(--red-500); }
.recbar__plus { transition: transform var(--t-mid) var(--ease-out), color var(--t-fast) var(--ease); }
.recbar.is-rec .recbar__btn--mic { background: var(--red-100); color: var(--red-500); }

.recbar__wave {
  flex: 0 0 0px;
  display: flex; align-items: center; justify-content: center;
  gap: 3px; height: 34px; padding: 0 var(--s-2);
  opacity: 0; min-width: 0; overflow: hidden;
  transition: flex-basis var(--t-mid) var(--ease-out), opacity var(--t-mid) var(--ease);
}
.recbar.is-rec .recbar__wave { flex: 1 1 0px; opacity: 1; }
.recbar__wave i {
  display: block; width: 3px; min-height: 4px;
  border-radius: var(--r-full);
  background: var(--red-500);
  height: 6px;
  transition: height 90ms linear;
}
.recbar__time {
  font-size: 12px; color: var(--red-500); font-variant-numeric: tabular-nums;
  min-width: 42px; text-align: right; flex: none;
  opacity: 0; transition: opacity var(--t-mid) var(--ease);
}
.recbar.is-rec .recbar__time { opacity: 1; }

/* --------------------------------------------------------------------------
   ⑦ 按钮弹出菜单 PopoverMenu
   契约：以按钮为原点展开 · 点外部原路收回
   -------------------------------------------------------------------------- */
.pop { position: relative; }
.pop__panel {
  position: absolute; z-index: var(--z-pop);
  min-width: 168px; padding: 6px;
  background: var(--surface);
  border-radius: var(--r-md);
  box-shadow: 0 12px 32px var(--shadow-dark-s), 0 2px 8px var(--shadow-dark-s);
  /* 变换原点 = 按钮（左上），展开即"从按钮长出来" */
  transform-origin: top left;
  opacity: 0; transform: scale(.88) translateY(-4px);
  pointer-events: none;
  transition: opacity var(--t-fast) var(--ease),
              transform var(--t-mid) var(--ease-out);
}
.pop__panel.is-open { opacity: 1; transform: scale(1) translateY(0); pointer-events: auto; }
.pop__item {
  display: flex; align-items: center; gap: var(--s-2);
  width: 100%; padding: 8px 10px;
  border-radius: var(--r-sm);
  font-size: 14px; text-align: left; color: var(--text);
  transition: background var(--t-fast) var(--ease);
}
.pop__item:hover { background: var(--blue-50); color: var(--blue-700); }
.pop__item--danger:hover { background: var(--red-100); color: var(--red-700); }
.pop__sep { height: 1px; background: var(--grey-200); margin: 4px 0; }

/* --------------------------------------------------------------------------
   ⑧⑨⑩⑫⑬⑭ 遮罩层：Modal / Dialog / Sheet 共用
   -------------------------------------------------------------------------- */
.scrim {
  position: fixed; inset: 0;
  background: rgba(31, 40, 51, .34);
  backdrop-filter: blur(2px);
  z-index: var(--z-scrim);      /* 必须在浮层之下，见 ui.css 层级注释 */
  opacity: 0; pointer-events: none;
  transition: opacity var(--t-mid) var(--ease);
}
.scrim.is-open { opacity: 1; pointer-events: auto; }

/* ⑧ 卡片放大为居中面板 */
.modal {
  position: fixed; z-index: var(--z-modal);
  left: 50%; top: 50%;
  width: min(560px, calc(100vw - 32px));
  max-height: min(84vh, 720px);
  display: flex; flex-direction: column;
  background: var(--surface);
  border-radius: var(--r-xl);
  box-shadow: 0 24px 60px rgba(31,40,51,.22);
  transform: translate(-50%, -50%) scale(.4);
  opacity: 0;
  transition: transform var(--t-slow) var(--ease-out), opacity var(--t-mid) var(--ease);
  overflow: hidden;
}
.modal.is-open { transform: translate(-50%, -50%) scale(1); opacity: 1; }
.modal__head {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--grey-200);
  flex: none;
}
.modal__title { font-size: 16px; font-weight: 600; margin: 0; }
.modal__body { padding: var(--s-5); overflow-y: auto; flex: 1; }
.modal__x {
  margin-left: auto; width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-full); color: var(--text-3);
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.modal__x:hover { background: var(--grey-100); color: var(--text); }

/* ⑨ 确认弹窗：轻微回弹 + 确认后原地切换为完成态 */
.dialog { width: min(400px, calc(100vw - 40px)); }
.dialog.is-open { animation: dlg-back 380ms var(--ease-back); }
@keyframes dlg-back {
  0%   { transform: translate(-50%,-50%) scale(.7); opacity: 0; }
  100% { transform: translate(-50%,-50%) scale(1);  opacity: 1; }
}
.dialog__done {
  display: flex; flex-direction: column; align-items: center; gap: var(--s-3);
  padding: var(--s-6) var(--s-5);
}
/* 完成勾：描边动画，不是淡入 */
.dialog__check { width: 56px; height: 56px; }
.dialog__check circle { fill: var(--green-50); stroke: var(--green-500); stroke-width: 2; }
.dialog__check path {
  fill: none; stroke: var(--green-500); stroke-width: 3;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 40; stroke-dashoffset: 40;
}
.dialog.is-done .dialog__check path { animation: dlg-draw 460ms var(--ease-out) 120ms forwards; }
@keyframes dlg-draw { to { stroke-dashoffset: 0; } }

/* ⑫ 底栏上方展开清单 */
.sheet {
  position: fixed; z-index: var(--z-sheet);
  left: 0; right: 0; bottom: 0;
  max-height: 72vh;
  display: flex; flex-direction: column;
  background: var(--surface);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  box-shadow: 0 -12px 40px var(--shadow-dark-s);
  transform: translateY(102%);
  transition: transform var(--t-slow) var(--ease-out);
}
.sheet.is-open { transform: translateY(0); }
.sheet__grip { width: 38px; height: 4px; border-radius: 2px; background: var(--grey-300);
               margin: 10px auto 4px; flex: none; }
.sheet__head { display: flex; align-items: center; padding: var(--s-2) var(--s-5) var(--s-3);
               border-bottom: 1px solid var(--grey-200); flex: none; }
.sheet__body { overflow-y: auto; padding: var(--s-3) var(--s-5) var(--s-5); }

/* ⑪ 侧边抽屉：主页面右移缩小 */
.drawer {
  position: fixed; z-index: var(--z-drawer);
  left: 0; top: 0; bottom: 0;
  width: min(320px, 84vw);
  display: flex; flex-direction: column;
  background: var(--surface);
  box-shadow: 8px 0 32px var(--shadow-dark-s);
  transform: translateX(-102%);
  transition: transform var(--t-slow) var(--ease-out);
}
.drawer.is-open { transform: translateX(0); }
.drawer__head { display: flex; align-items: center; gap: var(--s-2);
                padding: var(--s-5); border-bottom: 1px solid var(--grey-200); flex: none; }
.drawer__body { padding: var(--s-4) var(--s-5); overflow-y: auto; flex: 1; }

/* 主页面被推走 */
.pushable {
  transition: transform var(--t-slow) var(--ease-out), filter var(--t-slow) var(--ease);
  transform-origin: left center;
}
.pushable.is-pushed { transform: translateX(58%) scale(.88); filter: brightness(.97); }

/* --------------------------------------------------------------------------
   ⑩ Tooltip：带指向箭头，数秒后自动收回
   -------------------------------------------------------------------------- */
.tip { position: relative; display: inline-flex; }
.tip__bubble {
  position: absolute; bottom: calc(100% + 9px); left: 50%;
  transform: translate(-50%, 4px);
  padding: 6px 10px;
  background: var(--text); color: #fff;
  font-size: 12.5px; line-height: 1.5; white-space: nowrap;
  border-radius: var(--r-sm);
  opacity: 0; pointer-events: none;
  transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease-out);
  z-index: var(--z-toast);
}
.tip__bubble.is-open { opacity: 1; transform: translate(-50%, 0); }
/* 箭头用伪元素：与气泡同色，四边各一条 */
.tip__bubble::after {
  content: ""; position: absolute; top: 100%; left: 50%;
  margin-left: -5px;
  border: 5px solid transparent;
  border-top-color: var(--text);
}
.tip--bottom .tip__bubble { bottom: auto; top: calc(100% + 9px); transform: translate(-50%,-4px); }
.tip--bottom .tip__bubble.is-open { transform: translate(-50%, 0); }
.tip--bottom .tip__bubble::after { top: auto; bottom: 100%; border-top-color: transparent;
                                    border-bottom-color: var(--text); }

/* --------------------------------------------------------------------------
   ⑬ 可展开分类 Tabs
   契约：单行 → 展开为网格 → 选中后收回并居中
   -------------------------------------------------------------------------- */
.etabs { position: relative; }
.etabs__row { display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: center; }
.etabs__grid {
  position: absolute; left: 0; right: 0; top: calc(100% + 8px);
  display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: var(--s-2);
  padding: var(--s-3);
  background: var(--surface);
  border-radius: var(--r-md);
  box-shadow: 0 12px 32px var(--shadow-dark-s), 0 2px 8px var(--shadow-dark-s);
  z-index: var(--z-pop);
  opacity: 0; transform: scale(.9) translateY(-8px);
  pointer-events: none; transform-origin: top center;
  transition: opacity var(--t-fast) var(--ease),
              transform var(--t-mid) var(--ease-out);
}
.etabs__grid.is-open { opacity: 1; transform: scale(1) translateY(0); pointer-events: auto; }
.etabs__cell {
  padding: 10px 6px; border-radius: var(--r-md);
  background: var(--grey-50);
  font-size: 13px; color: var(--text-2); text-align: center;
  transition: background var(--t-fast) var(--ease),
              color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.etabs__cell:hover { background: var(--blue-50); color: var(--blue-700); }
.etabs__cell:active { transform: scale(.95); }
.etabs__cell.is-on { background: var(--blue-500); color: var(--text-on); }
/* 收回并居中时的落点 */
.etabs__chip {
  display: inline-flex; align-items: center; gap: 5px;
  height: 32px; padding: 0 var(--s-3);
  border-radius: var(--r-full);
  background: var(--blue-500); color: var(--text-on);
  font-size: 13.5px; box-shadow: var(--neu-sm);
}

/* --------------------------------------------------------------------------
   ⑭ 日期弹出日历
   -------------------------------------------------------------------------- */
.datepop { position: relative; }
.datepop__panel {
  position: absolute; left: 0; top: calc(100% + 8px);
  width: 268px; padding: var(--s-3);
  background: var(--surface);
  border-radius: var(--r-md);
  box-shadow: 0 12px 32px var(--shadow-dark-s), 0 2px 8px var(--shadow-dark-s);
  z-index: var(--z-pop);
  opacity: 0; transform: translateY(-8px) scale(.97);
  pointer-events: none; transform-origin: top left;
  transition: opacity var(--t-fast) var(--ease), transform var(--t-mid) var(--ease-out);
}
.datepop__panel.is-open { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
.datepop__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--s-2); }
.datepop__title { font-size: 14px; font-weight: 600; }
.datepop__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.datepop__dow { font-size: 11px; color: var(--text-3); text-align: center; padding: 4px 0; }
.datepop__day {
  aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  border-radius: var(--r-sm); font-size: 13px; color: var(--text);
  font-variant-numeric: tabular-nums;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.datepop__day:hover { background: var(--blue-50); }
.datepop__day.is-on { background: var(--blue-500); color: var(--text-on); }
.datepop__day.is-out { color: var(--text-3); }
.datepop__day:disabled { color: var(--grey-300); cursor: not-allowed; }

/* --------------------------------------------------------------------------
   一处性反馈：发送/完成/加载图标（inline SVG，不引外部动画库）
   见 README §为什么不用 Lordicon
   -------------------------------------------------------------------------- */
.fb { display: inline-flex; align-items: center; justify-content: center; }
.fb path, .fb circle, .fb rect {
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.fb--send.is-fly { animation: fb-fly 620ms var(--ease-out) forwards; }
@keyframes fb-fly {
  0%   { transform: translate(0,0) scale(1) rotate(0); opacity: 1; }
  100% { transform: translate(18px,-22px) scale(.5) rotate(-24deg); opacity: 0; }
}
.fb--spin { animation: fb-spin 900ms linear infinite; }
@keyframes fb-spin { to { transform: rotate(360deg); } }
.fb--shake { animation: fb-shake 520ms var(--ease); }
@keyframes fb-shake {
  0%,100% { transform: rotate(0); }
  25% { transform: rotate(-11deg); }
  75% { transform: rotate(11deg); }
}

/* Toast */
.toast {
  position: fixed; left: 50%; bottom: 92px; z-index: var(--z-toast);
  transform: translate(-50%, 12px);
  padding: 10px 18px;
  background: var(--text); color: #fff;
  border-radius: var(--r-full); font-size: 13.5px;
  box-shadow: 0 8px 24px var(--shadow-dark-s);
  opacity: 0; pointer-events: none;
  transition: opacity var(--t-mid) var(--ease), transform var(--t-mid) var(--ease-out);
}
.toast.is-open { opacity: 1; transform: translate(-50%, 0); }

/* 列表项通用 */
.row-item {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3); border-radius: var(--r-md);
  transition: background var(--t-fast) var(--ease);
}
.row-item:hover { background: var(--grey-50); }
.grow { flex: 1; min-width: 0; }
.ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.muted { color: var(--text-3); }
.small { font-size: 12.5px; }
.mono { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }

/* ==========================================================================
   【第42轮】开发阶段的组件操作说明（.devnote）
   这些文字原先直接铺在页面里。实测手机视口 390x844 上，
   #btnRun（算区间）被挤到 y=1523，而屏幕只有 896 高 ——
   店主（真实使用场景就是拿着手机）要滚过 1,500px 才够得到主按钮。
   41 轮的浏览器测试都在 1280x900 桌面视口下跑，那个尺寸下够得着，
   而且 uitest 是 evaluate() 直接调函数、不是真的点屏幕上的按钮，
   所以桌面 + 编程式操作 = 永远发现不了这个问题。
   默认折叠；桌面端直接隐藏（它对店主没有价值）。
   不删文字 —— 删了将来自己也会忘这些组件怎么用。
   ========================================================================== */
.devnote{margin:8px 0;font-size:12.5px;color:var(--text-3);
        border:1px dashed var(--grey-300);border-radius:6px;
        padding:6px 10px}
.devnote summary{cursor:pointer;color:var(--text-2);
        list-style:none;user-select:none}
.devnote summary::-webkit-details-marker{display:none}
.devnote summary::before{content:"\25B8 ";display:inline-block}
.devnote[open] summary::before{content:"\25BE "}
.devnote[open]{border-style:solid}
@media (min-width:900px){.devnote{display:none}}
