/* ===== 页面骨架：侧边导航 / 页头 / 卡片墙 / 响应式 ===== */

/* ---------- 侧边导航 ---------- */
/* dvh 那一行是给移动端写的：100vh 在手机浏览器里等于"地址栏收起时"的高度，
   于是地址栏展开时页面比可见区高出一截，侧栏底部的"退出登录"被压到屏外。
   写两遍是兜底——不认 dvh 的浏览器在解析阶段就丢掉第二行，回落到 vh。 */
.app { display: flex; min-height: 100vh; min-height: 100dvh; }

.sidebar {
  width: 216px;
  flex: none;
  display: flex;
  flex-direction: column;
  background: var(--card);
  border-right: 1px solid var(--line);
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  transition: width .18s ease;
}
.app.collapsed .sidebar { width: 64px; }

/* min-width:0 不可省：flex 子项的 min-width 默认是 auto，会拒绝收缩到内容宽度以下，
   内部网格一旦比剩余空间宽，整个 .app 就被撑出横向滚动条。 */
.content { flex: 1; min-width: 0; }

/* ---------- 玩家栏：列表 ↔ 详情互斥 ----------
   两者不再并排，而是同一块地方的两个视图（切换逻辑见 features/users/detail.js 的
   showView）。这样存档编辑器的六个折叠分区能铺满整宽，列表也不必再被右列的内容下限
   拖着走。.tab-page 的 max-width 已经兜住超宽屏，不需要另给上限。 */
#userListView, #userDetailView { min-width: 0; }
/* 详情是整页视图，不是并排的小卡片：420px 的下限与空态虚线框都失去意义。 */
#userDetail { min-height: 0; }

.sidebar-head {
  flex: none;
  height: 60px;
  padding: 0 14px;
  display: flex;
  align-items: center;
  gap: 10px;
  border-bottom: 1px solid var(--line);
}
.sidebar-mark {
  flex: none;
  width: 28px;
  height: 28px;
  line-height: 28px;
  text-align: center;
  background: var(--primary);
  color: #fff;
  border-radius: var(--radius-sm);
  font-size: 15px;
}
.sidebar-brand {
  flex: 1;
  min-width: 0;
  color: var(--ink);
  font-size: 15px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
}
.sidebar-brand small { display: block; color: var(--hint); font-size: 11px; font-weight: 400; }

/* ---------- 导航项：栏目与底部动作共用一套外观 ---------- */
.tabs {
  flex: 1;
  min-height: 0;
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow-y: auto;
}
.tabs button,
.sidebar-foot button {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  width: 100%;
  height: 40px;
  padding: 0 10px;
  flex: none;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--muted);
  font-weight: 500;
  text-align: left;
  white-space: nowrap;
}
.tabs button:hover,
.sidebar-foot button:hover { background: var(--neutral-soft); color: var(--primary); }
.tabs button.active { background: var(--primary-soft); color: var(--primary); font-weight: 600; }

/* 固定宽度是收起后图标能对齐的前提：缺了它，每行的图标各按自己的字宽居中。 */
.nav-ico { flex: none; width: 20px; text-align: center; font-size: 15px; }
.nav-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

.sidebar-foot {
  flex: none;
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  border-top: 1px solid var(--line);
}
.admin-name {
  padding: 4px 10px;
  color: var(--hint);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 收起态：只留图标。旋转的是图标本身而不是按钮，否则文字会跟着倒过来。 */
.app.collapsed .sidebar-head { padding: 0; justify-content: center; }
.app.collapsed .sidebar-brand,
.app.collapsed .admin-name,
.app.collapsed .nav-text { display: none; }
.app.collapsed .tabs button,
.app.collapsed .sidebar-foot button { justify-content: center; padding: 0; }
.app.collapsed .sidebar-toggle .nav-ico { transform: rotate(180deg); }

/* ---------- 标签页与页头 ---------- */
.tab-page { display: none; padding: 24px 28px 40px; max-width: 1500px; margin: 0 auto; }
.tab-page.active { display: block; }

.page-heading {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 20px;
  margin-bottom: 18px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--line);
}
.page-heading h2 { margin: 0 0 4px; font-size: 22px; color: var(--ink); }
.page-heading p { margin: 0; color: var(--hint); font-size: 12px; }
.page-actions { display: flex; align-items: center; gap: 8px; flex: none; }

/* ---------- 卡片基底 ---------- */
.table-wrap,
.detail-panel,
.editor-panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
/* 表格容器不参与 hover 上浮：粘性表头一动就糊了。 */
.table-wrap { overflow: hidden; }

/* 卡片墙上会跟着鼠标"抬起来"的那类卡片 */
.card-lift { transition: transform .2s, box-shadow .2s; }
.card-lift:hover { transform: translateY(-2px); box-shadow: var(--shadow); }

/* ---------- 分栏布局 ---------- */
/* 只剩礼包/公告/告警这几栏在用（玩家栏改成了列表↔详情互斥的全宽视图，见 layout.css
   末尾那段说明）。左列定宽 440 + gap 16，右列下限写 0，合计 456 —— 981px 视口下
   正文区有 981−216(侧栏)−56(padding)=709px，排得下，不会横滚。 */
/* 右列写成 minmax(0, 1fr) 而不是裸 1fr：1fr 等价于 minmax(auto, 1fr)，
   轨道 min 是 auto 时格子项才拿内容最小宽度当自己的 min-width:auto。
   而右列裹着一层没有类名的裸 <div>（overflow: visible），它的内容是一张
   最小内容宽约 500px 的表格——于是 440+16+510+216+56=1238，窄一点的桌面窗口也横滚。
   min 侧写成 0 之后格子项自动最小尺寸归零，裸 div 收缩到轨道宽，
   里面的 .table-wrap 自己去内滚。左上角那条 min-width:0 注释说的是同一个机制。 */
.split-layout {
  display: grid;
  grid-template-columns: minmax(360px, 440px) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

/* ---------- 数据看板：KPI 行 + 图表墙 ---------- */
/* min(200px, 100%) 的 100% 不是装饰：auto-fill/auto-fit 至少要排一列，
   而轨道下限若大于容器宽，那一列照样溢出。包一层 min() 之后窄容器里
   退化成"一列占满"，保证排得下。固定多列模板（如 .user-layout）不能这么写，
   那种情况两轨各自对 100% 求值、和仍可超容器。 */
.kpi-row {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(200px, 100%), 1fr));
  gap: 16px;
  margin-bottom: 16px;
}
.chart-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(440px, 100%), 1fr));
  gap: 16px;
}
/* 分组小标题：把一组卡片在视觉上归到一起。跟 .chart-card h3 区分开——
   它描述的是"下面这一片是什么"，不是单张图的指标名，字号小一号、字重轻一点。 */
.section-subtitle {
  margin: 28px 0 6px;
  font-size: 15px;
  font-weight: 600;
  color: var(--muted, #6b7280);
  display: flex;
  align-items: center;
  gap: 8px;
}
/* 小标题下面那段口径说明。与 .muted 同色但允许换行加行高，
   因为这里经常要写两行解释（样本单位是什么）。 */
.section-subtitle + .muted {
  margin: 0 0 14px;
  line-height: 1.6;
}
.section-subtitle + .muted b {
  color: var(--text, #1f2937);
  font-weight: 600;
}
.chart-card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 16px;
  /* grid item 的 min-width 默认是 auto，含义是"不得窄于内容的最小宽度"。
     ECharts 给画布写的是**固定像素**宽度（初始化那一刻量到的宿主宽度），
     于是卡片在宽视口下量过一次尺寸之后就再也窄不回去：画布顶住卡片、
     卡片顶住 .chart-grid，页面从此横向滚动，窄屏媒体查询里的规则全部落空。
     显式声明 0 之后卡片跟着轨道走，随之触发的 window.resize 再把画布改成新宽度。
     对照 .kpi——它在 charts.css 里已经写了 min-width: 0，只是那里没有画布，症状不明显。 */
  min-width: 0;
  transition: transform .2s, box-shadow .2s;
}
.chart-card:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.chart-card.wide { grid-column: 1 / -1; }
.chart-card h3 { margin: 0 0 2px; font-size: 14px; font-weight: 600; color: var(--ink); }
.chart-card > p { margin: 0 0 10px; }

/* ---------- 窄屏顶栏与抽屉遮罩 ----------
   桌面端侧栏常驻可见，这两样整个不出现。
   抽屉的宽度固定（不做桌面那种折叠），所以 .sidebar-toggle 在窄屏一并藏掉。 */
.mobile-bar, .nav-scrim { display: none; }

/* ---------- 响应式 ---------- */
@media (max-width: 980px) {
  /* 锁住背景滚动，否则手指在遮罩上划会把后面的正文带着一起滚。
     ⚠️ 必须留在这个媒体查询里面：抽屉开着时把窗口拉宽到 980 以上，
     .nav-open 那个类还挂在 #appView 上，锁在外面就会让宽屏页面再也滚不动。 */
  body.nav-locked { overflow: hidden; }

  /* 窄屏下侧栏变成覆盖式抽屉，而不是上一版那个常驻的 64px 图标栏。
     图标栏的账很好算：320px 视口的正文只剩 220px，而 layout.css:198 又把折叠按钮藏了，
     用户没有任何办法让它让位。

     ⚠️ 下面这几条 .app.collapsed 覆写**必须**在 layout.css 前面那段 .app.collapsed 基础规则之后，
     并且 216px 要写成和基础 .sidebar 同值——否则用户上次在桌面上点过"收起导航栏"，
     手机上拉开抽屉会是一个只剩图标的窄条（那份偏好存在 localStorage 里，跟着设备走）。 */
  .app.collapsed .sidebar { width: 216px; }
  .app.collapsed .sidebar-brand,
  .app.collapsed .admin-name,
  .app.collapsed .nav-text { display: block; }
  .app.collapsed .sidebar-head { padding: 0 14px; justify-content: flex-start; }
  .app.collapsed .tabs button,
  .app.collapsed .sidebar-foot button { justify-content: flex-start; padding: 0 10px; }
  .app.collapsed .sidebar-toggle .nav-ico { transform: none; }

  .mobile-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    position: sticky;
    top: 0;
    z-index: 80;
    height: 52px;
    padding: 0 12px 0 8px;
    background: var(--card);
    border-bottom: 1px solid var(--line);
  }
  .mobile-bar button { width: 40px; padding: 0; border: 0; background: none; font-size: 18px; }
  .mobile-bar-title { display: grid; font-size: 15px; font-weight: 600; color: var(--ink); line-height: 1.2; }
  .mobile-bar-title small { font-size: 11px; font-weight: 400; color: var(--hint); }

  .sidebar-toggle { display: none; }

  /* 脱离文档流：.content 才拿得到全宽。恒定 fixed（而不是只在打开时），
     这样抽屉开合不会改变正文宽度，也就没必要去惊动 ECharts 重排。 */
  .sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    width: 216px;
    z-index: 90;
    transform: translateX(-100%);
    transition: transform .2s ease;
    box-shadow: var(--shadow-lg);
  }
  .app.nav-open .sidebar { transform: none; }

  .nav-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 85;
    background: rgba(15, 23, 42, .45);
    opacity: 0;
    pointer-events: none;
    transition: opacity .2s ease;
  }
  .app.nav-open .nav-scrim { opacity: 1; pointer-events: auto; }

  /* minmax(0,1fr) 不能省：这条才是手机端真正的开关，只改基准模板等于没修。
     格子项的自动最小尺寸归零之后，里面那张宽表格才会去 .table-wrap 里内滚。 */
  .split-layout { grid-template-columns: minmax(0, 1fr); }
  .tab-page { padding: 18px; }
  .chart-grid { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
  /* 表格的横向滚动归 tables.css 的 .table-wrap 管（它是后加载的，本文件里
     任何 .table-wrap 的 overflow 声明都盖不过它——上面第 144 行那条 hidden 同理）。 */
  .page-heading { align-items: stretch; flex-direction: column; }
  .page-heading h2 { font-size: 19px; }
  /* 只有还在用 .split-layout 的那几栏需要这条：.split-layout 在 ≤980px 塌成一列，
     详情卡片落在表单下面，得跟上面隔开。（玩家栏的详情已经是整页视图，不适用。） */
  .split-layout .detail-panel { margin-top: 14px; }
  .tab-page { padding: 16px 14px 32px; }
  /* 104px 下限让 320px 视口能排下两列，6 张磁贴从 6 行压到 3 行；
     gap 也从 16 收到 8，两列才装得下。 */
  .kpi-row {
    grid-template-columns: repeat(auto-fill, minmax(min(104px, 100%), 1fr));
    gap: 8px;
  }
}
