:root {
  --bg: #eef2f8;
  --surface: #ffffff;
  --surface-2: #f5f8fd;
  --border: #dde7f5;
  --text: #1c2b3a;
  --text-2: #5a6b80;
  --text-3: #5e6f88;
  --accent: #2f7cf6;
  --accent-soft: #eaf2fe;
  --green: #16a34a;
  --green-soft: #e8f7ee;
  --amber: #d97706;
  --amber-soft: #fdf3e3;
  --red: #e04444;
  --red-soft: #fdecec;
  /* 深色文字色：用于浅色底上的小号文字/徽标，保证对比度 ≥ 4.5:1（--green/--amber/
     --red/--accent 仍用于按钮、图标、图表，不参与正文文字） */
  --green-ink: #15803d;
  --amber-ink: #b45309;
  --red-ink: #c62828;
  --accent-ink: #1d5fd0;
  /* 品牌蓝 #2f7cf6 的填充变体：白字按钮/徽标压在 #2f7cf6 上只有 3.94:1，
     微调到 #2870e7 后为 4.60:1（肉眼几乎无差别）；--accent 仍用于图标、
     描边、选中底、图表等非白字场景 */
  --accent-fill: #2870e7;
  --sidebar: #ffffff;
  --sidebar-text: #51617a;
  --shadow: 0 6px 18px rgba(31, 77, 166, 0.08);
  --radius: 10px;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  min-height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB",
    "Microsoft YaHei", sans-serif;
  font-size: 14px;
  line-height: 1.5;
}

button,
input,
select,
textarea {
  font: inherit;
}

button {
  cursor: pointer;
}

svg {
  display: block;
}

.app-shell {
  display: grid;
  grid-template-columns: 232px 1fr;
  min-height: 100vh;
}

/* 折叠态：左栏收成图标栏（class 由 .app-shell 上的 is-nav-collapsed 控制） */
.app-shell.is-nav-collapsed {
  grid-template-columns: 64px 1fr;
}

.sidebar {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--sidebar);
  color: var(--sidebar-text);
  /* 上下留白分别交给 .sidebar-head 与 .sidebar-footer（吸顶/吸底要能盖住滚动内容，
     padding 区不参与遮盖） */
  padding: 0 14px;
  overflow-y: auto;
  border-right: 1px solid var(--border);
}

/* 吸顶头：站点名称与菜单搜索固定在顶部，只让下面的菜单滚动（2026-09-16）。
   滚动时加投影，否则菜单像是「凭空被切掉」（is-scrolled 由 sidebar 的 scroll 监听切换）。 */
.sidebar-head {
  position: sticky;
  top: 0;
  z-index: 3;
  padding: 18px 0 0;
  background: var(--sidebar);
  transition: box-shadow 0.15s ease;
}

.sidebar-head.is-scrolled {
  box-shadow: 0 8px 12px -10px rgba(31, 77, 166, 0.55);
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 8px 18px;
  color: var(--text);
  font-weight: 700;
  font-size: 17px;
  letter-spacing: 0;
}

.brand a.site-name-link,
.login-brand a.site-name-link {
  display: flex;
  align-items: center;
  gap: 10px;
  color: inherit;
  text-decoration: none;
  min-width: 0;
}

.brand a.site-name-link:hover,
.login-brand a.site-name-link:hover {
  color: var(--accent);
}

.brand-mark {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  display: grid;
  place-items: center;
  background: var(--accent);
  color: #fff;
  flex: none;
}

.brand-mark svg {
  width: 17px;
  height: 17px;
}

/* 折叠开关（图标栏）：chevron 靠旋转指方向——展开态指左（收起），折叠态指右（展开） */
.nav-collapse {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  margin-left: auto;
  padding: 4px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-3);
}

.nav-collapse svg {
  width: 16px;
  height: 16px;
  transform: rotate(90deg);
  transition: transform 0.15s ease;
}

.nav-collapse:hover {
  background: var(--accent-soft);
  color: var(--accent-ink);
}

.nav-collapse.is-collapsed svg {
  transform: rotate(-90deg);
}

/* 折叠态：站点名与搜索框让位，只留图标（名字靠按钮 title 提示） */
.is-nav-collapsed .brand {
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 6px 0 10px;
}

.is-nav-collapsed .brand .site-name-link {
  justify-content: center;
}

.is-nav-collapsed .brand span:not(.brand-mark) {
  display: none;
}

.is-nav-collapsed .nav-collapse {
  margin-left: 0;
}

.is-nav-collapsed .nav-search {
  display: none;
}

.is-nav-collapsed .nav-item,
.is-nav-collapsed .nav-subitem {
  justify-content: center;
  gap: 0;
  padding-left: 0;
  padding-right: 0;
}

.is-nav-collapsed .nav-item > span,
.is-nav-collapsed .nav-subitem > span {
  display: none;
}

/* 图标栏里分组不再折叠：否则子菜单要点两下才看得见 */
.is-nav-collapsed .nav-submenu {
  display: block;
  padding-left: 0;
  padding-right: 0;
}

/* 折叠态：鼠标移到图标上立即弹出的菜单名（原生 title 有约 1s 延迟）。
   字号取 15px（侧边栏正文 13px），用户要求「字体加大点」。 */
.nav-tip {
  position: fixed;
  z-index: 80;
  max-width: 260px;
  padding: 6px 10px;
  border-radius: 7px;
  background: #16233a;
  color: #fff;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.3;
  white-space: nowrap;
  pointer-events: none;
  box-shadow: 0 8px 20px -10px rgba(15, 32, 60, 0.75);
}

/* 侧边栏菜单搜索（仅管理端；菜单一多就得滚动找） */
.nav-search {
  position: relative;
  margin: 0 2px 10px;
}

.nav-search svg {
  position: absolute;
  left: 10px;
  top: 50%;
  width: 15px;
  height: 15px;
  color: var(--text-3);
  transform: translateY(-50%);
  pointer-events: none;
}

.nav-search input {
  width: 100%;
  padding: 8px 30px 8px 32px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--text);
}

.nav-search input:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--surface);
}

.nav-search-clear {
  position: absolute;
  right: 5px;
  top: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 3px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--text-3);
  transform: translateY(-50%);
}

.nav-search-clear svg {
  position: static;
  width: 14px;
  height: 14px;
  transform: none;
}

.nav-search-clear:hover {
  background: var(--accent-soft);
  color: var(--accent-ink);
}

.nav-empty {
  padding: 10px 4px;
  color: var(--text-3);
  font-size: 12px;
  text-align: center;
}

.nav-group + .nav-group {
  margin-top: 4px;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  margin: 2px 0;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--sidebar-text);
  text-align: left;
  font-size: 13px;
}

.nav-item svg {
  width: 17px;
  height: 17px;
  flex: none;
}

.nav-item:hover {
  background: var(--accent-soft);
  color: var(--accent-ink);
}

.nav-item.active {
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-weight: 600;
}

.nav-group + .nav-group {
  margin-top: 2px;
}

.nav-parent .nav-chevron {
  margin-left: auto;
  display: inline-flex;
  color: var(--text-3);
  transition: transform 0.15s ease;
}

.nav-parent .nav-chevron svg {
  width: 15px;
  height: 15px;
}

.nav-parent.active {
  background: var(--surface-2);
  color: var(--text);
}

.nav-parent.active .nav-chevron {
  color: var(--text-2);
}

.nav-group.open > .nav-parent .nav-chevron {
  transform: rotate(180deg);
}

.nav-submenu {
  display: none;
  padding: 2px 0 4px 8px;
}

.nav-group.open > .nav-submenu {
  display: block;
}

.nav-subitem {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 7px 10px;
  margin: 1px 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-2);
  text-align: left;
  font-size: 12.5px;
  font-weight: 500;
}

.nav-subitem svg {
  width: 16px;
  height: 16px;
  flex: none;
}

.nav-subitem:hover {
  background: var(--accent-soft);
  color: var(--accent-ink);
}

.nav-subitem.active {
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-weight: 600;
}

/* 吸底栏：退出登录固定在下沿（padding 区同样不参与遮盖，所以底部留白落在这里） */
.sidebar-footer {
  position: sticky;
  bottom: 0;
  z-index: 3;
  margin-top: auto;
  padding: 16px 0 18px;
  background: var(--sidebar);
  transition: box-shadow 0.15s ease;
}

.sidebar-footer.is-scrolled {
  box-shadow: 0 -8px 12px -10px rgba(31, 77, 166, 0.55);
}

.sidebar-footer .nav-item {
  color: var(--text-3);
}

.main {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  height: 60px;
  padding: 0 28px;
  background: rgba(255, 255, 255, 0.92);
  border-bottom: 1px solid var(--border);
  backdrop-filter: blur(8px);
}

.page-title {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
}

.topbar-right {
  display: flex;
  align-items: center;
  gap: 10px;
}

.user-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-2);
  font-size: 12px;
}

.user-chip svg {
  width: 14px;
  height: 14px;
}

.content {
  padding: 24px 28px 40px;
}

.page-progress {
  display: none;
  position: sticky;
  top: 60px;
  z-index: 30;
  height: 2px;
  overflow: hidden;
  background: transparent;
}

.page-progress.active {
  display: block;
}

.page-progress.active::after {
  content: "";
  display: block;
  width: 42%;
  height: 100%;
  background: var(--accent);
  animation: progress-slide 0.9s ease-in-out infinite;
}

@keyframes progress-slide {
  from {
    transform: translateX(-120%);
  }
  to {
    transform: translateX(360%);
  }
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
/* 页面加载骨架屏（少量、通用） */
.skeleton-wrap {
  padding: 4px 2px 20px;
}
.skeleton {
  position: relative;
  overflow: hidden;
  background: #e8edf5;
  border-radius: 6px;
}
.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);
  animation: skeleton-shimmer 1.4s ease-in-out infinite;
}
@keyframes skeleton-shimmer {
  to {
    transform: translateX(100%);
  }
}
.skeleton-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
  margin-bottom: 16px;
}
.skeleton-card {
  height: 96px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.skeleton-panel {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}
.skeleton-panel .skeleton-head {
  height: 48px;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
}
.skeleton-line {
  height: 12px;
}
.skeleton-line.w44 {
  width: 44%;
}
.skeleton-line.w72 {
  width: 72%;
}
.skeleton-row {
  height: 40px;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
}

/* 首屏骨架壳里的侧边栏占位（/api/meta 未返回时）：深色底上要用半透明白条，
   否则默认的浅灰骨架块在侧边栏里亮得刺眼。 */
.nav-skeleton {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 4px 6px;
}
.nav-skeleton .skeleton {
  height: 34px;
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.14);
}
.nav-skeleton .skeleton::after {
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.22), transparent);
}
/* 页面加载失败 */
.load-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 46px 20px;
  text-align: center;
}
.load-error > svg {
  width: 26px;
  height: 26px;
  color: var(--red-ink);
}
.load-error strong {
  font-size: 15px;
  color: var(--text);
}
.load-error .muted {
  max-width: 560px;
}

.toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  flex-wrap: wrap;
}

/* 工具栏里的 .field（如兑换码生成表单）改成标签与输入同行：
   避免纵向布局把整行撑高后，按钮相对输入框垂直错位 */
.toolbar .field {
  flex-direction: row;
  align-items: center;
  gap: 8px;
}

.toolbar .field label {
  flex: 0 0 auto;
  white-space: nowrap;
}

.toolbar .field input {
  flex: 1 1 auto;
  min-width: 0;
  width: auto;
}

/* 审计列表的关键词搜索框（一个框同时搜用户名与操作；下拉仍是独立的精确筛选） */
.filter-search {
  width: 240px;
  flex: 0 0 auto;
}

/* 时间筛选整组（一键范围按钮 + 起止日期）：整组一起换行，两个日期框不会被拆到两行 */
.time-filter {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
}

/* 单个时间边界 = 日期框 + 时下拉 + 分下拉。刻意不用 datetime-local：
   它在部分浏览器（旧 Firefox / Safari / 内置内核）没有弹层、只能手敲，用户反馈「选不了时间」。 */
.time-field {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
}

.time-field .time-date,
.time-field .time-part {
  height: 34px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
}

.time-field .time-date {
  width: 142px;
  padding: 0 6px;
}

.time-field .time-part {
  width: 62px;
  padding: 0 0 0 6px;
}

.time-sep {
  color: var(--text-3);
}

/* 时间范围：工具栏上只占一个字段（显示已选区间），点开面板里选「开始 / 结束」。
   面板内的两个边界仍是「原生日期框 + 时/分下拉」，与 §193 同一套控件（不依赖弹层）。 */
.time-range {
  position: relative;
  flex: 0 0 auto;
}

.time-range-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 300px;
  height: 34px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
}

/* 「开始时间 ~ 结束时间 📅」：没选时是浅灰占位文字，选完显示本地时间 */
.time-range-slot {
  min-width: 118px;
  text-align: left;
  white-space: nowrap;
}

.time-range-slot.is-empty {
  color: var(--text-3);
}

.time-range-tilde {
  flex: 0 0 auto;
  color: var(--text-3);
}

.time-range-cal {
  display: inline-flex;
  flex: 0 0 auto;
  margin-left: auto;
  color: var(--text-3);
}

.time-range-cal svg {
  width: 15px;
  height: 15px;
}

.time-range.is-open .time-range-btn {
  border-color: var(--accent);
}

.time-range-pop {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 40;
  flex-direction: column;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.time-range.is-open .time-range-pop {
  display: flex;
}

/* 面板里「开始 / 结束」竖排：横排两套要 550px 以上，面板会顶出工具栏 */
.time-range-pop .time-filter {
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

.time-range-foot {
  display: flex;
  justify-content: flex-end;
  padding-top: 8px;
  border-top: 1px solid var(--border);
}

/* 窄款关键词框：日志页的手机号筛选 */
.filter-search-sm {
  width: 150px;
  flex: 0 0 auto;
}

.search {
  position: relative;
  flex: 1;
  min-width: 220px;
  max-width: 340px;
}

.search svg {
  position: absolute;
  left: 11px;
  top: 50%;
  width: 15px;
  height: 15px;
  color: var(--text-3);
  transform: translateY(-50%);
  pointer-events: none;
}

.search input {
  width: 100%;
  padding: 8px 12px 8px 34px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
}

.range-group {
  display: inline-flex;
  align-items: center;
  gap: 0;
}

.range-group .btn {
  border-radius: 0;
}

.range-group .btn + .btn {
  margin-left: -1px;
}

.range-group .btn:first-child {
  border-radius: var(--radius) 0 0 var(--radius);
}

.range-group .btn:last-child {
  border-radius: 0 var(--radius) var(--radius) 0;
}

/* 分段控件内未选中的 .btn-ghost 默认是透明底+透明边框，落在浅色面板上会「消失」
   （如密钥列表「近30天 / 全部」）。这里补回面色与描边，让它看得出是可选按钮。 */
.range-group .btn-ghost {
  border-color: var(--border);
  background: var(--surface);
  color: var(--text-2);
}

.range-group .btn-ghost:hover {
  border-color: #cbd4dc;
  background: var(--surface-2);
  color: var(--text);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 34px;
  padding: 0 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font-size: 13px;
  white-space: nowrap;
}

.btn svg {
  width: 15px;
  height: 15px;
}

.btn:hover {
  border-color: #cbd4dc;
  background: var(--surface-2);
}

.btn-primary {
  border-color: var(--accent-fill);
  background: var(--accent-fill);
  color: #fff;
}

.btn-primary:hover {
  background: #1e63d0;
  border-color: #1e63d0;
}

.btn-danger {
  border-color: #f2c4c4;
  color: var(--red-ink);
  background: var(--red-soft);
}

.btn-danger:hover {
  border-color: var(--red);
}

/* 弱按钮：仍要保留可见的按钮外观——透明底 + 透明描边放进白色/浅色面板（panel-head、
   pager、toolbar、行操作等）就与背景融为一体，用户多次反馈「底色跟背景色一样导致不明显」。
   这里退回「白底 + 常规描边 + 次级文字色」，比 .btn 略轻但轮廓清晰。 */
.btn-ghost {
  border-color: var(--border);
  background: var(--surface);
  color: var(--text-2);
}
.btn-ghost:hover {
  border-color: var(--accent);
  background: var(--surface);
  color: var(--accent-ink);
}
/* 唯一保留无框语义的位置：模态头里的关闭按钮（图标按钮，加描边反而突兀） */
.modal-head .btn-ghost,
.modal-head .btn-ghost:hover {
  border-color: transparent;
  background: transparent;
  color: var(--text-2);
}

.btn-icon {
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 7px;
  background: transparent;
  color: var(--text-2);
}

.btn-icon:hover {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text);
}

.btn.is-loading {
  cursor: wait;
  opacity: 0.8;
  pointer-events: none;
}

.btn:disabled {
  opacity: 0.45;
  /* 仍要能 hover：禁用的删除按钮靠 title 说明「为什么不能删」，pointer-events:none 会吞掉提示 */
  cursor: not-allowed;
}

.btn .spinner {
  width: 14px;
  height: 14px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  opacity: 0.85;
}

.btn-icon.danger:hover {
  background: var(--red-soft);
  color: var(--red-ink);
  border-color: #f2c4c4;
}

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
  background: #f7fafd;
}

.panel-head h3 {
  margin: 0;
  font-size: 14px;
}

/* 面板标题里的筛选字段（上游对账「对账月份」）：居中、与标题同一行。
   .field 默认是「标签在上」的纵向块，直接放进横向的 panel-head 会撑高整行。 */
.panel-head .field {
  flex-direction: row;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  margin-right: auto;
}

.panel-head .field label {
  flex: 0 0 auto;
  white-space: nowrap;
  font-size: 14px;
}

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 14px;
  margin-bottom: 18px;
}

.stat-card {
  min-height: 104px;
  padding: 16px 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* 统计卡悬浮明细：整张卡片都是触发区（原先 title 只挂在数字上，鼠标稍偏就不出提示），
   且用自定义气泡即时显示，避开浏览器原生 title 的约 1s 延迟。 */
.stat-card[data-tip] {
  position: relative;
}

.stat-card[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  left: 0;
  top: calc(100% + 8px);
  z-index: 40;
  min-width: 160px;
  padding: 8px 10px;
  border-radius: 8px;
  background: #1c2b3a;
  color: #fff;
  font-size: 12px;
  line-height: 1.6;
  white-space: pre-line;
  text-align: left;
  box-shadow: 0 8px 22px rgba(15, 32, 51, 0.18);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.stat-card[data-tip]:hover::after,
.stat-card[data-tip]:focus-within::after {
  opacity: 1;
  visibility: visible;
}

.stat-label {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-2);
  font-size: 12px;
}

.stat-label svg {
  width: 30px;
  height: 30px;
  padding: 7px;
  border-radius: 50%;
  box-sizing: border-box;
  color: #fff;
  background: var(--accent);
  flex: none;
}

.stat-grid .stat-card:nth-child(8n+1) .stat-label svg { background: #ff8a00; }
.stat-grid .stat-card:nth-child(8n+2) .stat-label svg { background: #f26d7e; }
.stat-grid .stat-card:nth-child(8n+3) .stat-label svg { background: #2f9bf4; }
.stat-grid .stat-card:nth-child(8n+4) .stat-label svg { background: #f7b731; }
.stat-grid .stat-card:nth-child(8n+5) .stat-label svg { background: #22c11f; }
.stat-grid .stat-card:nth-child(8n+6) .stat-label svg { background: #0fbdb4; }
.stat-grid .stat-card:nth-child(8n+7) .stat-label svg { background: #a55eea; }
.stat-grid .stat-card:nth-child(8n+8) .stat-label svg { background: #1f4e79; }

.stat-value {
  font-size: 24px;
  font-weight: 700;
  letter-spacing: 0;
  line-height: 1.1;
}

.stat-sub {
  color: var(--text-3);
  font-size: 12px;
}

.usage-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  margin-top: 18px;
}

.table-wrap {
  max-height: 560px;
  overflow-y: auto;
  overflow-x: auto;
}

/* 空态也包在 .table-wrap 中（listEmptyHtml / 静态包裹层内直接空态）：
   高度由前端按视口统一施加，内容在区域内垂直居中，面板不随数据多少伸缩。 */
.table-wrap:has(> .empty) {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* 列表数据加载中（删除/筛选/查询等刷新期间统一指示）：表格区内旋转图标 + 文案，
   占满既有列表区高度（sizeAdaptiveListArea 内联高度保留），加载中不跳版 */
.table-wrap > .list-busy,
.vt-scroll > .list-busy,
.log-vscroll > .list-busy {
  height: 100%;
  min-height: 220px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  color: var(--text-2);
  font-size: 14px;
}

.list-busy .spinner {
  width: 20px;
  height: 20px;
  border: 2px solid var(--border, #dfe4ec);
  border-top-color: var(--accent, #3264ff);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

.log-vscroll {
  max-height: 560px;
  overflow-y: auto;
  overflow-x: auto;
}

.log-virtual th,
.log-virtual td {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 420px;
  height: 40px;
  padding: 8px 10px;
}

.log-virtual tr.pad td {
  border: 0;
  padding: 0;
  height: auto;
}

.log-virtual tr.pad:hover {
  background: transparent;
}

.vt-scroll {
  max-height: 560px;
  overflow-y: auto;
}

/* 用户/渠道等虚拟滚动表：列宽不再固定 1750px/预设（table-layout:auto + width:100%，
   列随内容自适应分配）；行高 40px 固定、虚拟滚动不依赖列宽，可安全去掉 */
.vt-table th,
.vt-table td {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 420px;
  height: 40px;
  padding: 8px 10px;
}

.vt-table tr.pad td {
  border: 0;
  padding: 0;
  height: auto;
}

.vt-table tr.pad:hover {
  background: transparent;
}

.pricing-table {
  min-width: 1180px;
}

/* 操作按钮列固定在最右：凡末列含 .row-actions（普通表 + 用户/渠道虚拟滚动表），
   横向滚动时按钮列贴在滚动区右侧不滚出可视区。列宽由内容自适应分配
   （table-layout:auto），内容多的列自然分得更宽；末行保留横线见 tbody 末行规则。
   操作列本身按「width 极小 + nowrap」只占按钮内容宽度：列少（表未超宽）时富余
   宽度全部分给数据列、操作列始终贴在最右缘，不再被均分撑宽导致按钮悬在中间。 */
.table-wrap table:has(td .row-actions) th:last-child {
  position: sticky;
  right: 0;
  z-index: 3;
  background: #eef4fc;
  max-width: none;
  width: 1px;
  white-space: nowrap;
}

.table-wrap table:has(td .row-actions) td:last-child {
  position: sticky;
  right: 0;
  z-index: 1;
  background: var(--surface);
  max-width: none;
  width: 1px;
  white-space: nowrap;
}

.table-wrap table:has(td .row-actions) td:last-child .row-actions {
  justify-content: flex-end;
}

.table-wrap table:has(td .row-actions) tbody tr:hover td:last-child {
  background: #f5f9ff;
}

.table-wrap table tbody tr.pad:hover td {
  background: transparent;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

th {
  padding: 10px 14px;
  text-align: left;
  color: var(--text-2);
  font-weight: 600;
  font-size: 12px;
  background: #eef4fc;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: 2;
}

td {
  padding: 11px 14px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}

tbody tr:last-child td {
  border-bottom: 1px solid var(--border);
}

tbody tr:hover {
  background: #f5f9ff;
}

.mono {
  font-family: "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
  font-size: 12px;
}

.muted {
  color: var(--text-2);
}

.nowrap {
  white-space: nowrap;
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 22px;
  padding: 0 8px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
}

.badge-green {
  background: var(--green-soft);
  color: var(--green-ink);
}

.badge-amber {
  background: var(--amber-soft);
  color: var(--amber-ink);
}

.badge-red {
  background: var(--red-soft);
  color: var(--red-ink);
}

.badge-gray {
  background: #eef1f4;
  color: var(--text-2);
}

.badge-blue {
  background: var(--accent-soft);
  color: var(--accent-ink);
}

.alias-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 22px;
  margin: 1px 4px 1px 0;
  padding: 0 8px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  background: var(--accent-soft);
  color: var(--accent-ink);
  white-space: nowrap;
}

.row-actions {
  display: flex;
  gap: 6px;
}

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 34px 20px;
  text-align: center;
  color: var(--text-3);
}

.empty svg {
  width: 18px;
  height: 18px;
  color: var(--text-3);
}

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(15, 22, 30, 0.48);
}

.modal {
  width: 100%;
  max-width: 660px;
  max-height: 88vh;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow);
}

.modal-head {
  position: sticky;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 30px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

.modal-head h3 {
  margin: 0;
  font-size: 15px;
}

.modal-body {
  padding: 22px 30px;
}

.modal > form {
  padding: 22px 30px;
}

.modal > form:has(> .modal-body) {
  padding: 0;
}

.confirm-modal {
  max-width: 480px;
}

.confirm-body {
  padding: 22px 30px 12px;
}

.confirm-message {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  color: var(--text);
  line-height: 1.6;
}

.confirm-message p {
  margin: 0;
  font-size: 13px;
}

.confirm-icon {
  flex: 0 0 34px;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: 8px;
  background: var(--surface-2);
  color: var(--text-2);
}

.confirm-icon svg {
  width: 17px;
  height: 17px;
}

.confirm-icon.danger {
  background: var(--red-soft);
  color: var(--red-ink);
}

.confirm-keyword-field,
/* 危险操作二次确认：当前账号密码输入框（confirmDialog 的 password 选项） */
.confirm-password-field {
  margin-top: 14px;
}

.confirm-keyword-field label,
.confirm-password-field label {
  display: block;
  margin-bottom: 6px;
  font-size: 12px;
  color: var(--text-2);
}

.confirm-keyword-field .sg-input,
.confirm-password-field .sg-input {
  width: 100%;
}

.announcement-content {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: 15px;
  line-height: 1.75;
  color: var(--text);
}

/* 富文本正文（2026-09-22）：公告文字改成富文本后，弹窗按 HTML 渲染。
   块的上下间距统一收在 margin 上，列表/引用/链接各自一套；正文保持 pre-wrap —
   历史纯文本里的换行（\n）照样当硬换行，不需要先做一次数据迁移。 */
.announcement-content p,
.announcement-content div {
  margin: 0 0 8px;
}
.announcement-content p:last-child,
.announcement-content div:last-child,
.announcement-content ul:last-child,
.announcement-content ol:last-child {
  margin-bottom: 0;
}
.announcement-content ul,
.announcement-content ol {
  margin: 8px 0;
  padding-left: 24px;
}
.announcement-content ul {
  list-style: disc;
}
.announcement-content ol {
  list-style: decimal;
}
.announcement-content li {
  margin: 2px 0;
}
.announcement-content blockquote {
  margin: 8px 0;
  padding: 2px 12px;
  border-left: 3px solid var(--border);
  color: var(--text-2);
}
.announcement-content a {
  color: var(--accent-ink);
  text-decoration: underline;
}
.announcement-content a:hover {
  color: var(--accent);
}

/* 公告弹窗（2026-09-22 §297 样式优化）：比通用弹窗宽一点，标题带铃铛，底部单个动作居中 */
.announcement-modal {
  max-width: 760px;
}
.announcement-modal .modal-head h3 {
  display: flex;
  align-items: center;
  gap: 8px;
}
.announcement-modal .modal-head h3 svg {
  width: 18px;
  height: 18px;
  color: var(--accent);
}
.announcement-modal .modal-foot {
  justify-content: center;
}
.announcement-stage {
  position: relative;
}

/* 设置页图片缩略图（公告图片 2026-09-22 / 用户端活动图片 2026-09-23 共用）：
   可逐张移除，公告弹窗里纵向展示原图 */
.announcement-images {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  min-height: 40px;
}
.announcement-image-item {
  position: relative;
  display: inline-flex;
  width: 120px;
  height: 80px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-2, #f5f8fd);
  overflow: hidden;
}
.announcement-image-item img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.announcement-image-item .btn-icon {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 24px;
  height: 24px;
  background: rgba(17, 24, 39, 0.62);
  color: #fff;
}
.announcement-image-item .btn-icon:hover {
  background: rgba(17, 24, 39, 0.85);
  border-color: transparent;
  color: #fff;
}
.announcement-image-item .btn-icon svg {
  width: 14px;
  height: 14px;
}
.announcement-images-actions {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 10px;
}
/* 公告弹窗顶部：图片区（多张 5 秒轮播）。
   高度跟着弹窗宽度走 16:9（旧版死高 280px，宽图上下留一大条灰），上限 56vh；
   切换时高度恒定，所以下面的文字不会被不同尺寸的图顶来顶去。 */
.announcement-carousel {
  position: relative;
  aspect-ratio: 16 / 9;
  max-height: 56vh;
  border-radius: 12px;
  background: var(--surface-2, #f5f8fd);
  box-shadow: inset 0 0 0 1px var(--border);
  overflow: hidden;
}
.announcement-slide {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  opacity: 0;
  transition: opacity 0.35s ease;
  pointer-events: none;
}
.announcement-slide.is-active {
  opacity: 1;
  pointer-events: auto;
}
.announcement-slide img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 6px;
}
/* 翻页箭头：多张时才有；默认可点区域够大，压在图上也看得清 */
.announcement-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.92);
  color: var(--text-2);
  box-shadow: 0 2px 10px rgba(31, 77, 166, 0.16);
  opacity: 0.9;
  transition: opacity 0.18s ease, color 0.18s ease;
}
.announcement-nav:hover,
.announcement-nav:focus-visible {
  opacity: 1;
  color: var(--accent);
}
.announcement-nav svg {
  width: 18px;
  height: 18px;
}
.announcement-nav.is-prev {
  left: 12px;
}
.announcement-nav.is-next {
  right: 12px;
}
/* ICONS.chevron 是向下箭头，旋转成左右 */
.announcement-nav.is-prev svg {
  transform: rotate(90deg);
}
.announcement-nav.is-next svg {
  transform: rotate(-90deg);
}
/* 「1 / 3」序号：右下角深色小药丸，浅色图/深色图上都读得清 */
.announcement-counter {
  position: absolute;
  right: 12px;
  bottom: 12px;
  padding: 2px 10px;
  border-radius: 999px;
  background: rgba(17, 24, 39, 0.55);
  color: #fff;
  font-size: 12px;
  line-height: 20px;
  letter-spacing: 0.3px;
}
.announcement-stage + .announcement-content {
  margin-top: 16px;
}
.announcement-carousel + .announcement-content {
  margin-top: 16px;
}
.announcement-carousel-dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  margin-top: 10px;
}
/* 圆点：按钮本身留 22px 命中区，视觉点用 ::after 画（8px→20px） */
.announcement-dot {
  position: relative;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.announcement-dot::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--border);
  transition: width 0.2s ease, background 0.2s ease;
}
.announcement-dot:hover::after {
  background: var(--text-3);
}
.announcement-dot.is-active::after {
  width: 20px;
  background: var(--accent);
}

/* ===== 用户端活动图片（2026-09-23）=====
   两块活动位：左侧菜单栏底部（退出登录上方）+ 顶部横幅（页面标题与账号之间）。
   各最多 3 张、单张 ≤300KB，多张按 5 秒自动轮播（圆点可点）。
   高度固定，所以切图时两侧布局不会跟着跳。 */
.activity-carousel {
  position: relative;
  overflow: hidden;
  border-radius: 10px;
  background: var(--surface-2, #f5f8fd);
  box-shadow: inset 0 0 0 1px var(--border);
}
.activity-slide {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.35s ease;
  pointer-events: none;
}
.activity-slide.is-active {
  opacity: 1;
  pointer-events: auto;
}
.activity-slide img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* 左侧活动位：窄栏，用 contain 不裁掉图上文字；折叠成图标栏/窄屏时整块隐藏 */
.activity-side {
  margin-bottom: 10px;
}
.activity-side .activity-carousel {
  height: 64px;
  padding: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}
.app-shell.is-nav-collapsed .activity-side {
  display: none;
}
/* 顶部横幅：跟页面标题抢空间时先让横让位（min-width:0 + 窄屏隐藏），标题永不被挤断 */
.topbar-activity {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 1000px;
}
/* 顶部横幅与左侧一致：无底色/无边框/无圆角。
   注意顶部横幅同一元素同时有 activity-carousel + topbar-activity 两个类（不是嵌套），
   选择器必须连写才覆盖通用 .activity-carousel 的底框。 */
.topbar-activity.activity-carousel {
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}
/* 顶部横幅尽可能还原图片原始大小：active 图参与布局撑起容器高度（不做固定高），
   非 active 图叠在上层淡入淡出；宽高自适应、超高时压回顶栏高度内。 */
.topbar-activity .activity-slide {
  position: static;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 1;
}
.topbar-activity .activity-slide:not(.is-active) {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
}
.topbar-activity .activity-slide img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 56px;
  object-fit: contain;
}
/* 圆点：叠在图上（底部居中），浅图深图都读得清；按钮自身留 18px 命中区 */
.activity-dots {
  position: absolute;
  left: 50%;
  bottom: 2px;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 2px;
  z-index: 2;
}
.activity-dot {
  position: relative;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.activity-dot::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.72);
  box-shadow: 0 0 0 1px rgba(17, 24, 39, 0.28);
  transition: width 0.2s ease, background 0.2s ease;
}
.activity-dot:hover::after {
  background: #fff;
}
.activity-dot.is-active::after {
  width: 14px;
  background: #fff;
}

.modal-foot {
  position: sticky;
  bottom: 0;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 14px 30px;
  border-top: 1px solid var(--border);
  background: var(--surface-2);
}

/* 设置类页面的吸底保存栏（配合前端 formActions()）：长表单不必滚到底才能保存。
   ⚠️ 必须放在 .panel 之外——.panel 的 overflow:hidden 会让 sticky 失效，
   所以按钮用 form="<id>" 关联表单。z-index 低于 .topbar(20)/.page-progress(30)。
   ⚠️ 选择器必须是 .panel.form-actions：表单内部原本就有 .form-actions
   （如找回密码弹窗的 .form-actions.split），通用选择器会把吸底栏的内边距/阴影
   套到那些弹窗动作行上（按钮相对输入框内缩 18px）。 */
.panel.form-actions {
  position: sticky;
  bottom: 0;
  z-index: 15;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 14px;
  padding: 12px 18px;
  box-shadow: 0 -6px 18px rgba(15, 34, 60, 0.08);
}

.qr-code {
  width: 220px;
  height: 220px;
  margin: 12px auto;
  display: block;
  border: 1px solid var(--border);
  border-radius: 6px;
}
.pay-brand {
  width: 26px;
  height: 26px;
  border-radius: 6px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
}

.pay-brand svg {
  width: 100%;
  height: 100%;
  display: block;
}

.pay-brand-alipay {
  background: #1677ff;
  font-weight: 700;
  font-size: 17px;
  line-height: 1;
}

.pay-brand-wechat {
  background: #07c160;
}

.qr-card {
  margin-top: 4px;
  padding: 14px 14px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  text-align: center;
}

.qr-card-head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.qr-title {
  font-size: 16px;
  font-weight: 700;
}

.qr-tip {
  margin: 8px 0 4px;
  font-size: 12px;
  color: var(--text-3);
}

.qr-card .qr-code {
  margin: 10px auto 2px;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 16px;
}

.form-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 18px;
}

.form-actions.split {
  justify-content: space-between;
}

.form-actions .btn {
  min-width: 96px;
}

.form-section {
  border-top: 1px solid var(--border);
  padding-top: 16px;
  margin-top: 18px;
}

.form-section h4 {
  margin: 0 0 12px;
  font-size: 13px;
  font-weight: 600;
}

.file-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}

/* 面板里居中的动作行（上游对账「上游单价导入」：导入按钮居中，点击后开弹窗选文件）。
   .panel 自身没有内边距，这里补上与 panel-head 一致的水平留白。 */
.action-center {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 18px;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.field.full {
  grid-column: 1 / -1;
}

.field label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-2);
}

/* 必填标识：label 末尾的红色 *。统一由 markRequiredFields() 渲染后追加（见 app.js），
   模板里不要手写 —— 条件必填（专票购方信息、企业抬头税号）切换时要能同步增删。 */
.req {
  margin-left: 2px;
  color: var(--red);
  font-weight: 700;
  cursor: help;
}

/* 必填未填的即时反馈：统一由 guardRequired() 加/移除（2 秒后自动褪去） */
.field input.is-invalid,
.field select.is-invalid,
.field textarea.is-invalid,
.confirm-body input.is-invalid {
  border-color: var(--red);
  background: var(--red-soft);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #f7f9fc;
  color: var(--text);
}

.field textarea {
  min-height: 76px;
  resize: vertical;
}

/* 原生上传控件（input[type=file]）：浏览器默认的「选择文件」按钮与本站按钮风格完全
   不同（用户反馈系统设置里平台图标/默认头像的选择文件按钮太丑）。这里把 input 自身
   中性化（同时覆盖 .field input 的边框/底色，注意选择器要带上限定词才能压过它），
   只保留 ::file-selector-button 并做成浅蓝次级按钮；-webkit- 旧别名单独成规则，
   避免不支持该伪元素的内核把整条规则丢弃。文件名文字用 --text-2，选中后可读。 */
input[type="file"],
.field input[type="file"],
.file-row input[type="file"],
.logo-upload-row input[type="file"],
.upload-actions input[type="file"] {
  width: auto;
  max-width: 100%;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--text-2);
  font-size: 13px;
  box-sizing: border-box;
}
input[type="file"]::file-selector-button {
  height: 34px;
  margin: 0 10px 0 0;
  padding: 0 14px;
  border: 1px solid #cfe0fd;
  border-radius: var(--radius);
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
input[type="file"]::file-selector-button:hover {
  border-color: var(--accent-ink);
  background: #dfeafd;
}
/* 只包住「按钮 + 文件名」：.field 是 column flex，默认 stretch 会把控件拉满整行，
   聚焦时那条描边就变成一个横贯弹窗的空框（看起来像残留的方框）。 */
.field input[type="file"] {
  align-self: flex-start;
}
/* 默认聚焦态：.field input:focus 只画 2px --accent-soft outline（对比度约 1.1），
   在无边框的 file 控件上完全是空框噪声，这里去掉，只保留键盘聚焦时的深色描边 */
.field input[type="file"]:focus {
  outline: none;
}
/* 键盘聚焦要看得见：特异性要压过 .field input:focus */
.field input[type="file"]:focus-visible,
input[type="file"]:focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 2px;
}
input[type="file"]::-webkit-file-upload-button {
  height: 34px;
  margin: 0 10px 0 0;
  padding: 0 14px;
  border: 1px solid #cfe0fd;
  border-radius: var(--radius);
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
}
input[type="file"]::-webkit-file-upload-button:hover {
  border-color: var(--accent-ink);
  background: #dfeafd;
}
/* 文件名较长时不要把同排的说明文字挤出容器 */
.file-row input[type="file"] {
  max-width: 340px;
}
.upload-actions input[type="file"] {
  max-width: 280px;
}

.field input:focus,
.field select:focus,
.field textarea:focus,
.search input:focus {
  outline: 2px solid var(--accent-soft);
  border-color: var(--accent);
}

/* 公告富文本编辑器（2026-09-22）：工具条 + contenteditable 正文 + 链接输入行。
   正文口径与弹窗一致（pre-wrap、列表缩进、链接样式），编辑时看到的就是弹出来的效果。
   放在 .field input 规则之后，避免同权重时被表单控件样式覆盖。 */
.richtext {
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #f7f9fc;
  overflow: hidden;
}

.richtext:focus-within {
  border-color: var(--accent);
  outline: 2px solid var(--accent-soft);
}

.richtext-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 2px;
  padding: 6px 8px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}

.richtext-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-2);
  font-family: inherit;
  font-size: 13px;
  line-height: 1;
}

.richtext-btn:hover {
  background: var(--accent-soft);
  color: var(--accent-ink);
}

.richtext-btn:focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 1px;
}

.richtext-btn svg {
  width: 16px;
  height: 16px;
}

.richtext-glyph {
  font-size: 14px;
  color: inherit;
}

.richtext-glyph.is-bold {
  font-weight: 700;
}

.richtext-glyph.is-italic {
  font-family: Georgia, "Times New Roman", serif;
  font-style: italic;
}

.richtext-glyph.is-underline {
  text-decoration: underline;
}

.richtext-glyph.is-strike {
  text-decoration: line-through;
}

.richtext-sep {
  width: 1px;
  height: 18px;
  margin: 0 4px;
  background: var(--border);
}

.richtext-link {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}

/* hidden 属性会被上面的 display:flex 顶掉，这里显式兜一道 */
.richtext-link[hidden] {
  display: none;
}

.field .richtext-link input {
  flex: 1 1 auto;
  min-width: 0;
  width: auto;
  padding: 6px 10px;
  background: var(--surface);
  font-size: 13px;
}

.field .richtext-link .btn {
  flex: 0 0 auto;
  min-width: 0;
  padding: 0 14px;
  height: 34px;
}

.richtext-editor {
  min-height: 128px;
  max-height: 320px;
  overflow: auto;
  padding: 10px 12px;
  color: var(--text);
  font-size: 14px;
  line-height: 1.7;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.richtext-editor:focus {
  outline: none;
}

.richtext-editor[data-empty="1"]::before {
  content: attr(data-placeholder);
  color: var(--text-3);
  pointer-events: none;
}

.richtext-editor p,
.richtext-editor div {
  margin: 0 0 4px;
}

.richtext-editor ul,
.richtext-editor ol {
  margin: 4px 0;
  padding-left: 22px;
}

.richtext-editor ul {
  list-style: disc;
}

.richtext-editor ol {
  list-style: decimal;
}

.richtext-editor a {
  color: var(--accent-ink);
  text-decoration: underline;
}

.richtext-editor blockquote {
  margin: 4px 0;
  padding-left: 10px;
  border-left: 3px solid var(--border);
  color: var(--text-2);
}

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 34px;
  font-size: 13px;
}

.checkbox-row input {
  width: 15px;
  height: 15px;
  accent-color: var(--accent);
}

/* 支付设置 → 充值页支付方式（2026-09-22 配置化）：一行一种方式 ——
   勾选＝在用户端充值页展示、输入框＝显示名称、等宽小字＝只读的渠道口径（前端不可改）。 */
.recharge-method-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.recharge-method-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 16px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-2);
}

.recharge-method-row .checkbox-row {
  min-height: 0;
  font-weight: 600;
}

.recharge-method-row input[data-method-label] {
  flex: 0 0 220px;
  height: 34px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
}

.recharge-method-row input[data-method-label]:focus {
  outline: none;
  border-color: var(--accent);
}

.recharge-method-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  color: var(--text-3);
}

.recharge-method-row .muted {
  flex-basis: 100%;
  margin: 0;
}

.checkbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 8px 14px;
}

.modality-option-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.modality-option-row input {
  flex: 1;
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  font-size: 14px;
}

.modality-option-row input:focus {
  outline: none;
  border-color: #4f7cff;
  box-shadow: 0 0 0 2px rgba(79, 124, 255, 0.15);
}

.modality-option-row .btn {
  flex: 0 0 auto;
}

.key-reveal {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px;
  border: 1px dashed #b8c5d2;
  border-radius: var(--radius);
  background: var(--surface-2);
}

.key-reveal code {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text);
  font-size: 12px;
}

.key-reveal .btn {
  flex: none;
}

.switch {
  position: relative;
  display: inline-block;
  width: 34px;
  height: 20px;
  border: 0;
  border-radius: 999px;
  background: #cbd4dc;
  transition: background 0.15s ease;
}

.switch::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  transition: transform 0.15s ease;
}

.switch.on {
  background: var(--green);
}

.switch.on::after {
  transform: translateX(14px);
}

.toast {
  position: fixed;
  top: 18px;
  left: 50%;
  z-index: 80;
  display: flex;
  align-items: center;
  gap: 9px;
  max-width: 360px;
  padding: 11px 14px;
  transform: translateX(-50%);
  border-radius: var(--radius);
  background: #18202a;
  color: #fff;
  font-size: 13px;
  box-shadow: var(--shadow);
  animation: toast-in 0.2s ease;
}

.toast.success svg {
  color: #4ade80;
}

.toast.error svg {
  color: #f87171;
}

.toast svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translate(-50%, 8px);
  }
  to {
    opacity: 1;
    transform: translate(-50%, 0);
  }
}

.login-wrap {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  background: linear-gradient(160deg, #eaf2fe 0%, #eef2f8 55%, #e6edf7 100%);
}

.login-card {
  width: 100%;
  max-width: 400px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow);
  padding: 30px 32px;
}

.login-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 24px;
}

.login-brand .brand-mark {
  width: 36px;
  height: 36px;
}

.login-brand strong {
  font-size: 18px;
}

.login-tip {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 14px;
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-2, #f7f8fa);
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-2);
}

.login-tip svg {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  margin-top: 1px;
}

.login-tip.warning {
  border-color: #f2b8b5;
  background: #fff1f0;
  color: #c0392b;
}

.login-tip.warning svg {
  color: #c0392b;
}

.login-card .field {
  margin-bottom: 14px;
}

.login-card .btn-primary {
  width: 100%;
  height: 38px;
  margin-top: 6px;
}

.login-switch {
  margin: 14px 0 0;
  text-align: center;
  font-size: 12px;
  color: var(--text-2);
}

.login-switch a {
  color: var(--accent-ink);
  text-decoration: none;
}

.pager {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 0 2px;
}

.pager .muted {
  flex: 1;
  text-align: center;
}



.recharge-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.amount-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.amount-chip {
  min-width: 52px;
  height: 32px;
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
}

.amount-chip:hover {
  border-color: var(--accent);
  color: var(--accent-ink);
  background: var(--accent-soft);
}

.integration-cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.example-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  overflow: hidden;
}

.example-head {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
}

.example-head svg {
  width: 15px;
  height: 15px;
  color: var(--accent-ink);
}

.example-head strong {
  flex: 1;
  font-size: 12px;
}

.example-head .btn {
  height: 28px;
  padding: 0 8px;
  font-size: 12px;
}

.example-card pre {
  margin: 0;
  padding: 12px;
  overflow-x: auto;
  color: #d7e2ec;
  background: #151b23;
  font-size: 12px;
  line-height: 1.5;
}

.example-card code {
  white-space: pre;
}

.invite-placeholder {
  padding: 38px 20px;
}

.invite-placeholder svg {
  width: 30px;
  height: 30px;
  color: var(--accent-ink);
}

.invite-placeholder h3 {
  margin: 12px 0 4px;
  color: var(--text);
  font-size: 17px;
}

.invite-placeholder p {
  max-width: 440px;
  margin: 0;
  color: var(--text-2);
  font-size: 13px;
}

.invite-steps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.invite-steps > div {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 11px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--text-2);
}

.invite-steps strong {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-size: 12px;
}

.obs-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  gap: 14px;
  margin-bottom: 18px;
}

.webhook-delivery-panel .webhook-summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
  padding: 18px 18px 0;
}

.webhook-delivery-panel .webhook-summary-grid .stat-card {
  min-height: 86px;
  padding: 13px 14px;
}

.webhook-delivery-panel .webhook-delivery-body {
  padding: 18px;
}

.webhook-delivery-panel .webhook-delivery-body > .empty {
  margin: 0;
}

.trace-panel .trace-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}

.trace-panel .trace-toolbar .search {
  max-width: 360px;
}

.trace-panel .trace-toolbar .btn {
  flex: none;
}

.trace-panel #trace-result {
  padding: 0;
}

.trace-panel #trace-result > .stat-grid {
  padding: 18px 18px 0;
  margin-bottom: 0;
}

.trace-panel #trace-result > .empty {
  padding: 22px 18px;
}

.btn-link {
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--accent-ink);
  cursor: pointer;
}

.btn-link:hover {
  text-decoration: underline;
}

.obs-error-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  padding: 14px;
}

.obs-error-block h4 {
  margin: 0 0 8px;
  color: var(--text-2);
  font-size: 12px;
}

.obs-latency-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(110px, 1fr));
  gap: 10px;
  padding: 14px;
  overflow-x: auto;
}

.obs-latency-item {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
}

.obs-latency-item span {
  color: var(--text-2);
  font-size: 12px;
}

.obs-latency-item strong {
  font-size: 18px;
  font-weight: 700;
}

.notification-template-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.notification-template-item {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
}

.notification-template-head {
  min-height: 20px;
}

.notification-template-head strong {
  color: var(--text);
  font-size: 13px;
}

.notification-history-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.notification-history-toolbar {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 8px;
}

.notification-history-item {
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
}

.notification-history-item.unread {
  border-left: 3px solid var(--accent);
}

.notification-history-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.notification-history-title strong {
  min-width: 0;
  color: var(--text);
  font-size: 13px;
}

.notification-history-title .btn {
  flex: none;
  padding: 4px 8px;
  font-size: 11px;
}

.notification-status {
  flex: none;
  color: var(--text-2);
  font-size: 11px;
}

.notification-history-item p {
  margin: 6px 0;
  color: var(--text-2);
  font-size: 12px;
}

.notification-time {
  color: var(--text-3);
  font-size: 11px;
}

.danger-zone {
  border-color: #f2c4c4;
}

.danger-zone .panel-head {
  background: var(--red-soft);
  border-bottom-color: #f2c4c4;
}

.danger-zone .panel-head h3 {
  color: var(--red-ink);
}

.danger-zone-body {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
}

.danger-zone-body strong {
  color: var(--red-ink);
}

.danger-zone-body p {
  max-width: 680px;
  margin: 4px 0 0;
  color: var(--text-2);
  font-size: 12px;
}

.danger-zone-body .btn {
  flex: none;
}

@media (max-width: 1180px) {
  /* 窄屏优先保页面标题与账号：活动横幅整块收掉（不做半截挤压） */
  .topbar-activity {
    display: none;
  }
}

@media (max-width: 860px) {
  .app-shell {
    grid-template-columns: 68px 1fr;
  }

  /* 左栏在这个断点被压成图标栏，活动图没有展示空间 */
  .activity-side {
    display: none;
  }

  .brand {
    justify-content: center;
    padding: 6px 0 14px;
  }

  .brand span,
  .nav-item span,
  .nav-subitem span,
  .nav-chevron {
    display: none;
  }

  .nav-item,
  .nav-subitem {
    justify-content: center;
    padding: 10px;
  }

  .nav-subitem {
    padding: 8px;
  }

  .nav-submenu {
    padding-left: 0;
  }

  .content,
  .topbar {
    padding-left: 18px;
    padding-right: 18px;
  }

  .form-grid {
    grid-template-columns: 1fr;
  }

  .usage-grid {
    grid-template-columns: 1fr;
  }

  .obs-grid,
  .obs-error-grid {
    grid-template-columns: 1fr;
  }

  .trace-panel .trace-toolbar {
    align-items: stretch;
    flex-direction: column;
  }

  .trace-panel .trace-toolbar .search {
    max-width: none;
  }

  .trace-panel .trace-toolbar .btn {
    width: 100%;
  }

  /* 公告弹窗：窄屏把内边距与箭头收小，别让 36px 的圆钮压住图片 */
  .announcement-modal .modal-body {
    padding-left: 16px;
    padding-right: 16px;
  }

  .announcement-nav {
    width: 30px;
    height: 30px;
  }

  .announcement-nav.is-prev {
    left: 6px;
  }

  .announcement-nav.is-next {
    right: 6px;
  }

  .announcement-counter {
    right: 8px;
    bottom: 8px;
  }

  .announcement-content {
    font-size: 14px;
  }

  .obs-latency-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .notification-template-grid {
    grid-template-columns: 1fr;
  }

  .notification-history-title {
    align-items: flex-start;
    flex-direction: column;
    gap: 6px;
  }

  .danger-zone-body {
    align-items: stretch;
    flex-direction: column;
  }

  .recharge-grid,
  .integration-cards,
  .invite-steps {
    grid-template-columns: 1fr;
  }
}

.password-row {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
}

.password-row input {
  flex: 1;
  min-width: 0;
}

.password-toggle {
  flex: none;
  color: var(--text-2);
}

.password-toggle.active {
  color: var(--accent-ink);
}

.captcha-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
}

.captcha-input {
  flex: 1;
  min-width: 0;
}

.captcha-box {
  min-width: 130px;
  min-height: 46px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.captcha-box:hover {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.captcha-box svg {
  display: block;
}

/* 菜单管理（2026-09-10 重做：角色分段页签 + 分组卡片 + 实时启用计数） */
.menu-config {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.menu-rolebar {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 6px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
}
.menu-tab {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border: 1px solid transparent;
  border-radius: 9px;
  background: transparent;
  color: var(--text-2);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.menu-tab:hover {
  background: var(--surface);
  color: var(--text);
}
.menu-tab.is-active {
  background: var(--surface);
  border-color: var(--border);
  color: var(--accent-ink);
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(31, 77, 166, 0.06);
}
.menu-tab-count {
  min-width: 38px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-3);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.menu-tab.is-active .menu-tab-count {
  background: var(--accent-soft);
  color: var(--accent-ink);
}
.menu-role-tab {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.menu-role-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}
.menu-role-chip {
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-size: 12px;
  font-weight: 500;
}
.menu-role-count {
  font-size: 13px;
  color: var(--text-2);
}
.menu-role-count b {
  color: var(--accent-ink);
  font-variant-numeric: tabular-nums;
}
.menu-role-actions {
  display: flex;
  gap: 8px;
  margin-left: auto;
}
.menu-order-reset {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.menu-order-reset svg {
  width: 14px;
  height: 14px;
}
.menu-search {
  max-width: 220px;
}
.menu-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 14px;
}
.menu-card {
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  overflow: hidden;
}
.menu-card--single {
  grid-column: 1 / -1;
}
.menu-card.is-filtered-empty {
  display: none;
}
/* 拖动排序手柄（分组卡片与菜单项共用）；touch-action: none 避免触屏拖动被页面滚动抢走 */
.menu-drag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-3);
  cursor: grab;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}
.menu-drag svg {
  width: 14px;
  height: 14px;
  display: block;
}
.menu-drag:hover {
  color: var(--accent);
  background: var(--accent-soft);
}
.menu-drag:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  color: var(--accent);
}
.menu-drag:active {
  cursor: grabbing;
}
.menu-drag--card {
  margin-left: -2px;
}
/* 拖动中的元素：半透明 + 虚线边；关掉命中测试，避免 elementFromPoint 命中自己 */
.menu-card.is-dragging,
.menu-item.is-dragging {
  opacity: 0.55;
  border-style: dashed;
  border-color: var(--accent);
  pointer-events: none;
}
/* 改菜单名的小按钮（分组卡片头 / 菜单项行）：平时压低存在感，hover/聚焦才点亮 */
.menu-rename {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-3);
  opacity: 0.55;
  cursor: pointer;
  transition: opacity 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.menu-rename svg {
  width: 13px;
  height: 13px;
  display: block;
}
.menu-rename:hover,
.menu-rename:focus-visible {
  opacity: 1;
  color: var(--accent);
  background: var(--accent-soft);
}
.menu-rename:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.menu-rename--card {
  margin-left: 2px;
}

/* 菜单管理「改图标」弹窗：图标网格（图标 + 名称，点一下即替换） */
.modal-icon-picker {
  width: min(720px, 92vw);
}

.icon-choice-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  gap: 8px;
  max-height: 52vh;
  overflow-y: auto;
}

.icon-choice {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 6px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text-2);
  font-size: 12px;
}

.icon-choice svg {
  width: 20px;
  height: 20px;
}

.icon-choice:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-ink);
}

.icon-choice.is-on {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-ink);
  box-shadow: inset 0 0 0 1px var(--accent);
}
body.is-menu-dragging {
  cursor: grabbing;
  -webkit-user-select: none;
  user-select: none;
}
.menu-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px 7px 7px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}
.menu-card-toggle {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  padding: 4px 6px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.menu-card-toggle:hover {
  background: var(--surface);
}
.menu-caret {
  display: inline-flex;
  color: var(--text-3);
}
.menu-caret svg {
  width: 15px;
  height: 15px;
  display: block;
  transition: transform 0.15s ease;
}
.menu-card.is-collapsed .menu-caret svg {
  transform: rotate(-90deg);
}
.menu-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: var(--accent-soft);
  color: var(--accent-ink);
  flex: none;
}
.menu-card-icon svg {
  width: 14px;
  height: 14px;
  display: block;
}
.menu-card-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.menu-card-count {
  font-size: 11px;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
  flex: none;
}
.menu-card.is-empty .menu-card-count {
  color: var(--amber-ink);
}
.menu-all {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-2);
  font-size: 12px;
  cursor: pointer;
  flex: none;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.menu-all:hover {
  border-color: var(--accent);
  color: var(--accent-ink);
}
.menu-all input {
  width: 13px;
  height: 13px;
  accent-color: var(--accent);
}
.menu-role-all {
  padding: 5px 12px;
  font-size: 13px;
  font-weight: 500;
}
.menu-card-body {
  display: grid;
  /* 菜单项里拖动柄 + 勾选框 + 图标 + 改名按钮再加间距/内边距要占掉约 104px，
     列宽下限给到 170px 才能保证名称至少还有一格可读宽度。名称仍然**折行而不是
     截断**（改过的名字要能看全，改名上限 24 字），所以窄视口下个别长名会占两行，
     这是刻意取舍。 */
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: 8px;
  padding: 12px;
}
.menu-card.is-collapsed .menu-card-body {
  display: none;
}
.menu-item {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding: 8px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface);
  color: var(--text-2);
  font-size: 13px;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.menu-item:hover {
  border-color: var(--accent);
  color: var(--text);
}
.menu-item.is-on {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--text);
  font-weight: 500;
}
.menu-item input {
  width: 14px;
  height: 14px;
  accent-color: var(--accent);
  flex: none;
}
.menu-item-icon {
  display: inline-flex;
  color: var(--text-3);
  flex: none;
}
.menu-item.is-on .menu-item-icon {
  color: var(--accent-ink);
}
.menu-item-icon svg {
  width: 14px;
  height: 14px;
  display: block;
}
.menu-item-label {
  flex: 1;
  min-width: 0;
  line-height: 1.25;
  overflow-wrap: anywhere;
}
.menu-item.sg-hidden {
  display: none;
}
.menu-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 26px 12px;
  color: var(--text-3);
  font-size: 13px;
}
.menu-empty svg {
  width: 15px;
  height: 15px;
}
.menu-savebar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 18px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.menu-savehint {
  flex: 1;
  font-size: 12px;
  color: var(--text-3);
}
#menu-config-form.is-dirty .menu-savehint {
  color: var(--amber-ink);
  font-weight: 500;
}

/* 严格 style-src：内联 style 属性迁移为工具类（2026-08-24） */
.sg-mt18 { margin-top: 18px; }
.sg-mt12 { margin-top: 12px; }
.sg-mt20 { margin-top: 20px; }
.sg-mt4 { margin-top: 4px; }
.sg-mt4w100 { margin-top: 4px; width: 100%; }
.sg-mt0 { margin-top: 0; padding-top: 0; border-top: 0; }
.sg-ml6 { margin-left: 6px; }
.sg-p10 { margin: 10px 0 0; }
.sg-p10s { margin: 10px 0 0; font-size: 12px; }
.sg-p12s { margin: 12px 0 0; font-size: 12px; }
.sg-fs12 { font-size: 12px; }
.sg-fs12m4 { font-size: 12px; margin: 4px 0 0; }
.sg-fwb.sg-fwb { font-weight: bold; }
.sg-mono { font-family: monospace; }
.sg-input { height: 34px; border: 1px solid var(--border); border-radius: var(--radius); padding: 0 10px; background: var(--surface); }
.sg-success { color: var(--green-ink); }
.sg-muted { color: var(--text-3); }
.sg-danger { color: var(--red-ink); }
.sg-mw170 { max-width: 170px; }
.sg-mw140 { max-width: 140px; }
.sg-minw120 { min-width: 120px; }
.sg-grid3 { grid-template-columns: repeat(3, 1fr); }
.sg-grid2 { grid-template-columns: 1fr 1fr; }
.sg-flexgap { display: flex; gap: 8px; align-items: stretch; }
.sg-flex1 { flex: 1; }
.sg-hidden { display: none; }
#role-form-area { display: none; }

/* 平台图标（站点 Logo 上传，2026-08-30） */
.brand-logo {
  width: 26px;
  height: 26px;
  object-fit: contain;
  display: block;
}
.brand-mark:has(.brand-logo) {
  background: transparent;
}
.login-brand .brand-logo {
  width: 28px;
  height: 28px;
}
.logo-upload-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.logo-upload-row .brand-mark {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  background: var(--accent);
}
.logo-upload-row .brand-mark:has(.brand-logo) {
  background: var(--bg-soft, #f2f4f8);
}
.logo-upload-row .brand-logo {
  width: 32px;
  height: 32px;
}
/* 站点信息：平台图标与默认头像同排（2026-09-04） */
.media-upload-field {
  min-width: 0;
}
.media-upload-field .logo-upload-row {
  flex: 1;
}
.upload-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  min-width: 0;
  flex: 1;
}
/* 头像（用户上传 / 系统默认头像，2026-09-01） */
.user-avatar {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  object-fit: cover;
  display: block;
}
.avatar-preview {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-2, #f5f8fd);
  border: 1px solid var(--border);
  flex-shrink: 0;
}
.avatar-preview img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  display: block;
}
.field-hint {
  margin-top: 6px;
  font-size: 12px;
  color: var(--text-3);
}
.field-hint.full {
  grid-column: 1 / -1;
}

/* 密钥弹窗「所属用户」：按用户名/手机号搜索的选择器 */
.user-picker {
  position: relative;
}
.user-picker-box {
  position: relative;
}
.user-picker .search {
  min-width: 0;
  max-width: none;
}
/* .field input 的 padding 简写晚于 .search input 定义，会把左内边距压回 12px
   导致放大镜图标盖住输入内容；这里用更高特异性补回图标位留白 */
.user-picker .search input {
  padding: 8px 12px 8px 34px;
}
.user-picker-list.sg-hidden {
  display: none;
}
.user-picker-list {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 90;
  max-height: 240px;
  overflow: auto;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.user-picker-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  padding: 7px 9px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  text-align: left;
  font-size: 13px;
  cursor: pointer;
}
.user-picker-item:hover {
  background: var(--surface-2);
}
.user-picker-item.active {
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-weight: 600;
}
.user-picker-meta {
  font-size: 12px;
  color: var(--text-3);
}
.user-picker-item.active .user-picker-meta {
  color: var(--accent-ink);
  opacity: 0.85;
}
.user-picker-empty {
  padding: 8px 9px;
  font-size: 12px;
  color: var(--text-3);
}

/* 用户端模型列表筛选：模态多选下拉 */
.filter-drop {
  position: relative;
}
.filter-menu.sg-hidden {
  display: none;
}
.filter-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 80;
  min-width: 190px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.filter-menu .filter-opt {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 6px 8px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  text-align: left;
  font-size: 13px;
}
.filter-menu .filter-opt:hover {
  background: var(--surface-2);
}
.filter-menu .filter-opt.active {
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-weight: 600;
}
.filter-menu .filter-opt .check {
  display: inline-block;
  width: 14px;
  text-align: center;
  color: var(--accent-ink);
}
.filter-menu-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 4px;
  padding-top: 6px;
  border-top: 1px solid var(--border);
}
.filter-menu-actions .btn {
  padding: 3px 10px;
  font-size: 12px;
}
.filter-badge {
  display: inline-block;
  min-width: 16px;
  margin-left: 4px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--accent-fill);
  color: #fff;
  font-size: 11px;
  line-height: 16px;
  text-align: center;
}

/* —— UI 细节优化轮（2026-09-06）—— */

/* 数字列统一：金额/用量/请求数等数值列右对齐 + 等宽数字（渲染时按列头语义 + 行内容
   样本自动识别并施加 th.num/td.num；手机号/ID/密钥等文本列不参与）。 */
th.num,
td.num {
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* 行内图标操作按钮：多按钮操作列（用户/渠道）次要操作改图标，列宽显著收窄 */
.btn-icon-sm {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  flex: none;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--surface);
  color: var(--text-2);
}

.btn-icon-sm svg {
  width: 15px;
  height: 15px;
}

.btn-icon-sm:hover {
  border-color: #cbd4dc;
  background: var(--surface-2);
  color: var(--text);
}

.btn-icon-sm.danger {
  border-color: #f2c4c4;
  color: var(--red-ink);
  background: var(--red-soft);
}

.btn-icon-sm.danger:hover {
  border-color: var(--red);
}

/* 非破坏性图标操作（如「删除用户数据」）：蓝色信息样式，与红色「删除」区分 */
.btn-icon-sm.info {
  border-color: #cfe0fd;
  color: var(--accent-ink);
  background: var(--accent-soft);
}

.btn-icon-sm.info:hover {
  border-color: var(--accent);
}

/* 普通表（非虚拟滚动）超长单元格兜底：单格最多撑到 420px，超长文本在格内折行，
   不再把整表撑到数千 px（删除超长行导致的整表列宽“抖动”在源头被消除）；
   操作列（.row-actions 末列）与 mono 标识类（单号/ID/密钥等）保持原样横向可滚。 */
.table-wrap table:not(.vt-table) td {
  max-width: 420px;
  overflow-wrap: anywhere;
}

.table-wrap table:not(.vt-table) td.mono {
  max-width: none;
  overflow-wrap: normal;
}

/* 账单/消费概览表（2026-09-23）：列多（用户端 9 列 / 管理端 13 列），
   在窄面板（如用户端控制台的消费概览）里会被数字列挤压，「月份」显示不全/换行。
   设最小整宽让超宽部分走横向滚动，月份列保持 nowrap。 */
.billing-table {
  min-width: 780px;
}
.billing-table th:first-child,
.billing-table td:first-child {
  white-space: nowrap;
}

/* 弹窗打开时锁定背景滚动，避免遮罩下层内容随滚轮移动 */
body.modal-open {
  overflow: hidden;
}

/* 列表列宽拖拽（col-resizable 由前端接线时加类）：th 右缘 9px 拖拽条，
   悬停高亮 + col-resize 光标；末列（含 sticky 操作列）右缘不设拖拽条。
   注意 th 必须保持 position:sticky（此前误改 relative 会让表头在列表滚动时
   不再吸顶）；sticky 同为定位元素，::after 拖拽条照常锚定在 th 上。 */
table.col-resizable th:not(:last-child) {
  position: sticky;
  top: 0;
}

table.col-resizable th:not(:last-child)::after {
  content: "";
  position: absolute;
  top: 0;
  right: -4px;
  width: 9px;
  height: 100%;
  cursor: col-resize;
  z-index: 6;
}

table.col-resizable th:not(:last-child)::after:hover {
  background: rgba(47, 124, 246, 0.18);
}

/* 接入示例按模态 Tab 切换（2026-09-07）：文本/图像/视频/向量/音频 五类示例
   的胶囊 Tab + 面板显隐，布局变量与 .example-card/.btn 体系保持一致。 */
.integration-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 12px 0 16px;
}
.integration-tab {
  height: 32px;
  padding: 0 16px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-2);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}
.integration-tab:hover {
  border-color: #cbd4dc;
  background: var(--surface-2);
  color: var(--text);
}
.integration-tab.active {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-ink);
}
.integration-pane .integration-cards {
  margin: 0;
}
.modality-arrow {
  white-space: nowrap;
  font-weight: 600;
  color: var(--text);
  cursor: help;
}
.modality-arrow-sep {
  color: var(--accent);
  font-weight: 700;
}
.integration-modality-note {
  margin: 0 0 10px;
  padding: 8px 12px;
  border-left: 3px solid var(--accent);
  background: var(--surface-2);
  border-radius: 0 6px 6px 0;
}

/* 模型折扣（2026-09-22）：价格单元格「划线原价 + 折后价 + 8折标签」（参考千问定价页）。
   数据口径见 app/model_discount.py：售价字段 = 原价，折后价 = 原价 × 折扣 = 用户实付。 */
.price-cell {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
.price-original {
  color: var(--text-3);
  text-decoration: line-through;
}
.price-now {
  font-weight: 600;
}
.price-discount-chip {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 6px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-size: 12px;
  line-height: 1;
  white-space: nowrap;
}

/* 模型售价页 →「折扣设置」弹窗：一行一个模型（折数 + 备注 + 折后实价预览）。
   2026-09-22：一行现在要放「模型 + 输入/输出/缓存原价 + 折数 + 备注」，通用弹窗 660px 太窄
   （价格文案被挤到折行），这里单独放宽到 1000px；小屏仍按 100% 宽自适应。 */
.discount-modal {
  max-width: 1000px;
}
.discount-hint {
  margin-bottom: 12px;
  line-height: 1.6;
}
.discount-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 52vh;
  overflow: auto;
  padding-right: 4px;
  /* 打开弹窗时会按这里的自然高度写死一个 inline height（见 openModelDiscountsModal）：
     搜索过滤后行数变少，列表与整个弹窗的高度保持不变，不会上下跳。 */
  box-sizing: border-box;
}
.discount-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 12px;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-2);
}
.discount-row .discount-model {
  display: flex;
  flex-direction: column;
  /* 一行里价格文案最长（输入/输出/缓存三档），给它最多空间，别让它挤到折行 */
  flex: 1 1 320px;
  min-width: 0;
}
.discount-row .discount-model strong {
  overflow-wrap: anywhere;
}
.discount-list-price {
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-3);
}
/* .discount-row 的 display:flex 定义在 .sg-hidden 之后，会把 display:none 顶掉，
   搜索过的行必须显式再盖一层，否则「搜不到时全部隐藏」只是加了 class 但还画着 */
.discount-row.sg-hidden {
  display: none;
}
/* 折数输入框 + 单位「折」绑成一组（换行时不拆散），整组跟左侧文字块垂直居中 */
.discount-fold {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 6px;
}
.discount-fold .discount-unit {
  font-size: 13px;
  white-space: nowrap;
}
.discount-row input.discount-input {
  flex: 0 0 132px;
  height: 34px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
}
.discount-row input.discount-input:focus {
  outline: none;
  border-color: var(--accent);
}
/* 折后预览跟模型名/原价同列（不再独占一行）：折数输入框才能跟整块文字上下居中 */
.discount-preview {
  margin-top: 2px;
  font-size: 12px;
  color: var(--text-3);
}
.discount-preview-error {
  color: var(--red-ink);
}

/* 折扣设置弹窗的批量工具区（2026-09-22）：
   上面「全部折扣设置」= 一次改所有模型，下面「批量折扣设置」= 只改勾选的那些行。
   两边都只做「预填折数 + 即时预览」，落库仍走底部「保存折扣」（只提交改动行），
   所以误点一下按钮不会动到线上价格，取消弹窗即可放弃。 */
.discount-tools {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 12px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-2);
}
.discount-tools-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.discount-tools-label {
  flex: 0 0 104px;
  font-size: 13px;
  font-weight: 600;
}
.discount-tools input[type="number"] {
  flex: 0 0 88px;
  height: 34px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
}
.discount-tools input[type="number"]:focus {
  outline: none;
  border-color: var(--accent);
}
.discount-tools-dirty {
  font-size: 12px;
}
.discount-tools-actions {
  display: flex;
  gap: 8px;
  margin-left: auto;
}

/* 行首勾选框（批量折扣设置用） */
.discount-row input[data-discount-select] {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  accent-color: var(--accent);
}

/* 折扣设置弹窗的搜索行（238 上模型上百个，先搜再改）：复用平台 .search，
   放大镜有统一的 15px 尺寸，别再自己造一套（2026-09-22 曾把图标撑成 40px）。 */
.discount-search-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}
.discount-search-row .search {
  flex: 1 1 auto;
  max-width: none;
}
.discount-count {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--text-3);
}
